mod_auth_openidc-2.3.3/src/mod_auth_openidc.c0000644000076500000240000036251113203320522021112 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Initially based on mod_auth_cas.c: * https://github.com/Jasig/mod_auth_cas * * Other code copied/borrowed/adapted: * shared memory caching: mod_auth_mellon * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu * **************************************************************************/ #include "apr_hash.h" #include "apr_strings.h" #include "ap_config.h" #include "ap_provider.h" #include "apr_lib.h" #include "apr_file_io.h" #include "apr_sha1.h" #include "apr_base64.h" #include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_protocol.h" #include "http_request.h" #include "mod_auth_openidc.h" // TODO: // - sort out oidc_cfg vs. oidc_dir_cfg stuff // - rigid input checking on discovery responses // - check self-issued support // - README.quickstart // - refresh metadata once-per too? (for non-signing key changes) extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * clean any suspicious headers in the HTTP request sent by the user agent */ static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix, apr_hash_t *scrub) { const int prefix_len = claim_prefix ? strlen(claim_prefix) : 0; /* get an array representation of the incoming HTTP headers */ const apr_array_header_t * const h = apr_table_elts(r->headers_in); /* table to keep the non-suspicious headers */ apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts); /* loop over the incoming HTTP headers */ const apr_table_entry_t * const e = (const apr_table_entry_t *) h->elts; int i; for (i = 0; i < h->nelts; i++) { const char * const k = e[i].key; /* is this header's name equivalent to a header that needs scrubbing? */ const char *hdr = (k != NULL) && (scrub != NULL) ? apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL; const int header_matches = (hdr != NULL) && (oidc_strnenvcmp(k, hdr, -1) == 0); /* * would this header be interpreted as a mod_auth_openidc attribute? Note * that prefix_len will be zero if no attr_prefix is defined, * so this will always be false. Also note that we do not * scrub headers if the prefix is empty because every header * would match. */ const int prefix_matches = (k != NULL) && prefix_len && (oidc_strnenvcmp(k, claim_prefix, prefix_len) == 0); /* add to the clean_headers if non-suspicious, skip and report otherwise */ if (!prefix_matches && !header_matches) { apr_table_addn(clean_headers, k, e[i].val); } else { oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k, e[i].val); } } /* overwrite the incoming headers with the cleaned result */ r->headers_in = clean_headers; } /* * scrub all mod_auth_openidc related headers */ void oidc_scrub_headers(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->scrub_request_headers != 0) { const char *prefix = oidc_cfg_claim_prefix(r); apr_hash_t *hdrs = apr_hash_make(r->pool); if (apr_strnatcmp(prefix, "") == 0) { if ((cfg->white_listed_claims != NULL) && (apr_hash_count(cfg->white_listed_claims) > 0)) hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims, hdrs); else oidc_warn(r, "both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!"); } char *authn_hdr = oidc_cfg_dir_authn_header(r); if (authn_hdr != NULL) apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr); /* * scrub all headers starting with OIDC_ first */ oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs); /* * then see if the claim headers need to be removed on top of that * (i.e. the prefix does not start with the default OIDC_) */ if ((strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) { oidc_scrub_request_headers(r, prefix, NULL); } } } /* * strip the session cookie from the headers sent to the application/backend */ void oidc_strip_cookies(request_rec *r) { char *cookie, *ctx, *result = NULL; const char *name = NULL; int i; apr_array_header_t *strip = oidc_dir_cfg_strip_cookies(r); char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r)); if ((cookies != NULL) && (strip != NULL)) { oidc_debug(r, "looking for the following cookies to strip from cookie header: %s", apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA)); cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx); do { while (cookie != NULL && *cookie == OIDC_CHAR_SPACE) cookie++; for (i = 0; i < strip->nelts; i++) { name = ((const char**) strip->elts)[i]; if ((strncmp(cookie, name, strlen(name)) == 0) && (cookie[strlen(name)] == OIDC_CHAR_EQUAL)) { oidc_debug(r, "stripping: %s", name); break; } } if (i == strip->nelts) { result = result ? apr_psprintf(r->pool, "%s%s%s", result, OIDC_STR_SEMI_COLON, cookie) : cookie; } cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx); } while (cookie != NULL); oidc_util_hdr_in_cookie_set(r, result); } } #define OIDC_SHA1_LEN 20 /* * calculates a hash value based on request fingerprint plus a provided nonce string. */ static char *oidc_get_browser_state_hash(request_rec *r, const char *nonce) { oidc_debug(r, "enter"); /* helper to hold to header values */ const char *value = NULL; /* the hash context */ apr_sha1_ctx_t sha1; /* Initialize the hash context */ apr_sha1_init(&sha1); /* get the X-FORWARDED-FOR header value */ value = oidc_util_hdr_in_x_forwarded_for_get(r); /* if we have a value for this header, concat it to the hash input */ if (value != NULL) apr_sha1_update(&sha1, value, strlen(value)); /* get the USER-AGENT header value */ value = oidc_util_hdr_in_user_agent_get(r); /* if we have a value for this header, concat it to the hash input */ if (value != NULL) apr_sha1_update(&sha1, value, strlen(value)); /* get the remote client IP address or host name */ /* int remotehost_is_ip; value = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NOLOOKUP, &remotehost_is_ip); apr_sha1_update(&sha1, value, strlen(value)); */ /* concat the nonce parameter to the hash input */ apr_sha1_update(&sha1, nonce, strlen(nonce)); /* concat the token binding ID if present */ value = oidc_util_get_provided_token_binding_id(r); if (value != NULL) { oidc_debug(r, "Provided Token Binding ID environment variable found; adding its value to the state"); apr_sha1_update(&sha1, value, strlen(value)); } /* finalize the hash input and calculate the resulting hash output */ unsigned char hash[OIDC_SHA1_LEN]; apr_sha1_final(hash, &sha1); /* base64url-encode the resulting hash and return it */ char *result = NULL; oidc_base64url_encode(r, &result, (const char *) hash, OIDC_SHA1_LEN, TRUE); return result; } /* * return the name for the state cookie */ static char *oidc_get_state_cookie_name(request_rec *r, const char *state) { return apr_psprintf(r->pool, "%s%s", OIDC_STATE_COOKIE_PREFIX, state); } /* * return the static provider configuration, i.e. from a metadata URL or configuration primitives */ static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *c, oidc_provider_t **provider) { json_t *j_provider = NULL; char *s_json = NULL; /* see if we should configure a static provider based on external (cached) metadata */ if ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) { *provider = &c->provider; return TRUE; } oidc_cache_get_provider(r, c->provider.metadata_url, &s_json); if (s_json == NULL) { if (oidc_metadata_provider_retrieve(r, c, NULL, c->provider.metadata_url, &j_provider, &s_json) == FALSE) { oidc_error(r, "could not retrieve metadata from url: %s", c->provider.metadata_url); return FALSE; } oidc_cache_set_provider(r, c->provider.metadata_url, s_json, apr_time_now() + (c->provider_metadata_refresh_interval <= 0 ? apr_time_from_sec( OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : c->provider_metadata_refresh_interval)); } else { /* correct parsing and validation was already done when it was put in the cache */ oidc_util_decode_json_object(r, s_json, &j_provider); } *provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t)); memcpy(*provider, &c->provider, sizeof(oidc_provider_t)); if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) { oidc_error(r, "could not parse metadata from url: %s", c->provider.metadata_url); if (j_provider) json_decref(j_provider); return FALSE; } json_decref(j_provider); return TRUE; } /* * return the oidc_provider_t struct for the specified issuer */ static oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg *c, const char *issuer, apr_byte_t allow_discovery) { /* by default we'll assume that we're dealing with a single statically configured OP */ oidc_provider_t *provider = NULL; if (oidc_provider_static_config(r, c, &provider) == FALSE) return NULL; /* unless a metadata directory was configured, so we'll try and get the provider settings from there */ if (c->metadata_dir != NULL) { /* try and get metadata from the metadata directory for the OP that sent this response */ if ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery) == FALSE) || (provider == NULL)) { /* don't know nothing about this OP/issuer */ oidc_error(r, "no provider metadata found for issuer \"%s\"", issuer); return NULL; } } return provider; } /* * find out whether the request is a response from an IDP discovery page */ static apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg *cfg) { /* * prereq: this is a call to the configured redirect_uri, now see if: * the OIDC_DISC_OP_PARAM is present */ return oidc_util_request_has_parameter(r, OIDC_DISC_OP_PARAM) || oidc_util_request_has_parameter(r, OIDC_DISC_USER_PARAM); } /* * return the HTTP method being called: only for POST data persistence purposes */ static const char *oidc_original_request_method(request_rec *r, oidc_cfg *cfg, apr_byte_t handle_discovery_response) { const char *method = OIDC_METHOD_GET; char *m = NULL; if ((handle_discovery_response == TRUE) && (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, cfg))) && (oidc_is_discovery_response(r, cfg))) { oidc_util_get_request_parameter(r, OIDC_DISC_RM_PARAM, &m); if (m != NULL) method = apr_pstrdup(r->pool, m); } else { /* * if POST preserve is not enabled for this location, there's no point in preserving * the method either which would result in POSTing empty data on return; * so we revert to legacy behavior */ if (oidc_cfg_dir_preserve_post(r) == 0) return OIDC_METHOD_GET; const char *content_type = oidc_util_hdr_in_content_type_get(r); if ((r->method_number == M_POST) && (apr_strnatcmp(content_type, OIDC_CONTENT_TYPE_FORM_ENCODED) == 0)) method = OIDC_METHOD_FORM_POST; } oidc_debug(r, "return: %s", method); return method; } /* * send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage */ apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method) { if (oidc_cfg_dir_preserve_post(r) == 0) return FALSE; oidc_debug(r, "enter"); oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *method = oidc_original_request_method(r, cfg, FALSE); if (apr_strnatcmp(method, OIDC_METHOD_FORM_POST) != 0) return FALSE; /* read the parameters that are POST-ed to us */ apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params) == FALSE) { oidc_error(r, "something went wrong when reading the POST parameters"); return FALSE; } const apr_array_header_t *arr = apr_table_elts(params); const apr_table_entry_t *elts = (const apr_table_entry_t*) arr->elts; int i; char *json = ""; for (i = 0; i < arr->nelts; i++) { json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json, oidc_util_escape_string(r, elts[i].key), oidc_util_escape_string(r, elts[i].val), i < arr->nelts - 1 ? "," : ""); } json = apr_psprintf(r->pool, "{ %s }", json); const char *jmethod = "preserveOnLoad"; const char *jscript = apr_psprintf(r->pool, " \n", jmethod, json, location ? apr_psprintf(r->pool, "window.location='%s';\n", location) : ""); if (location == NULL) { if (javascript_method) *javascript_method = apr_pstrdup(r->pool, jmethod); if (javascript) *javascript = apr_pstrdup(r->pool, jscript); } else { oidc_util_html_send(r, "Preserving...", jscript, jmethod, "

Preserving...

", DONE); } return TRUE; } /* * restore POST parameters on original_url from HTML5 local storage */ static int oidc_request_post_preserved_restore(request_rec *r, const char *original_url) { oidc_debug(r, "enter: original_url=%s", original_url); const char *method = "postOnLoad"; const char *script = apr_psprintf(r->pool, " \n", method, original_url); const char *body = "

Restoring...

\n" "
\n"; return oidc_util_html_send(r, "Restoring...", script, method, body, DONE); } /* * parse state that was sent to us by the issuer */ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c, const char *state, oidc_proto_state_t **proto_state) { char *alg = NULL; oidc_debug(r, "enter: state header=%s", oidc_proto_peek_jwt_header(r, state, &alg)); oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, c->provider.client_secret, oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; oidc_jwt_t *jwt = NULL; if (oidc_jwt_parse(r->pool, state, &jwt, oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk), &err) == FALSE) { oidc_error(r, "could not parse JWT from state: invalid unsolicited response: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully parsed JWT from state"); if (jwt->payload.iss == NULL) { oidc_error(r, "no \"%s\" could be retrieved from JWT state, aborting", OIDC_CLAIM_ISS); oidc_jwt_destroy(jwt); return FALSE; } oidc_provider_t *provider = oidc_get_provider_for_issuer(r, c, jwt->payload.iss, FALSE); if (provider == NULL) { oidc_jwt_destroy(jwt); return FALSE; } /* validate the state JWT, validating optional exp + iat */ if (oidc_proto_validate_jwt(r, jwt, provider->issuer, FALSE, FALSE, provider->idtoken_iat_slack) == FALSE) { oidc_jwt_destroy(jwt); return FALSE; } char *rfp = NULL; if (oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_RFP, TRUE, &rfp, &err) == FALSE) { oidc_error(r, "no \"%s\" claim could be retrieved from JWT state, aborting: %s", OIDC_CLAIM_RFP, oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); return FALSE; } if (apr_strnatcmp(rfp, OIDC_PROTO_ISS) != 0) { oidc_error(r, "\"%s\" (%s) does not match \"%s\", aborting", OIDC_CLAIM_RFP, rfp, OIDC_PROTO_ISS); oidc_jwt_destroy(jwt); return FALSE; } char *target_link_uri = NULL; oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_TARGET_LINK_URI, FALSE, &target_link_uri, NULL); if (target_link_uri == NULL) { if (c->default_sso_url == NULL) { oidc_error(r, "no \"%s\" claim could be retrieved from JWT state and no " OIDCDefaultURL " is set, aborting", OIDC_CLAIM_TARGET_LINK_URI); oidc_jwt_destroy(jwt); return FALSE; } target_link_uri = c->default_sso_url; } if (c->metadata_dir != NULL) { if ((oidc_metadata_get(r, c, jwt->payload.iss, &provider, FALSE) == FALSE) || (provider == NULL)) { oidc_error(r, "no provider metadata found for provider \"%s\"", jwt->payload.iss); oidc_jwt_destroy(jwt); return FALSE; } } char *jti = NULL; oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_JTI, FALSE, &jti, NULL); if (jti == NULL) { char *cser = oidc_jwt_serialize(r->pool, jwt, &err); if (cser == NULL) return FALSE; if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, cser, &jti) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); return FALSE; } } char *replay = NULL; oidc_cache_get_jti(r, jti, &replay); if (replay != NULL) { oidc_error(r, "the \"%s\" value (%s) passed in the browser state was found in the cache already; possible replay attack!?", OIDC_CLAIM_JTI, jti); oidc_jwt_destroy(jwt); return FALSE; } /* jti cache duration is the configured replay prevention window for token issuance plus 10 seconds for safety */ apr_time_t jti_cache_duration = apr_time_from_sec( provider->idtoken_iat_slack * 2 + 10); /* store it in the cache for the calculated duration */ oidc_cache_set_jti(r, jti, jti, apr_time_now() + jti_cache_duration); oidc_debug(r, "jti \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds", jti, apr_time_sec(jti_cache_duration)); jwk = NULL; if (oidc_util_create_symmetric_key(r, c->provider.client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; oidc_jwks_uri_t jwks_uri = { provider->jwks_uri, provider->jwks_refresh_interval, provider->ssl_validate_server }; if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri, oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) { oidc_error(r, "state JWT could not be validated, aborting"); oidc_jwt_destroy(jwt); return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully verified state JWT"); *proto_state = oidc_proto_state_new(); oidc_proto_state_set_issuer(*proto_state, jwt->payload.iss); oidc_proto_state_set_original_url(*proto_state, target_link_uri); oidc_proto_state_set_original_method(*proto_state, OIDC_METHOD_GET); oidc_proto_state_set_response_mode(*proto_state, provider->response_mode); oidc_proto_state_set_response_type(*proto_state, provider->response_type); oidc_proto_state_set_timestamp_now(*proto_state); oidc_jwt_destroy(jwt); return TRUE; } static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c, const char *currentCookieName) { char *cookie, *tokenizerCtx; char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r)); if (cookies != NULL) { cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx); while (cookie != NULL) { while (*cookie == OIDC_CHAR_SPACE) cookie++; if (strstr(cookie, OIDC_STATE_COOKIE_PREFIX) == cookie) { char *cookieName = cookie; while (cookie != NULL && *cookie != OIDC_CHAR_EQUAL) cookie++; if (*cookie == OIDC_CHAR_EQUAL) { *cookie = '\0'; cookie++; if ((currentCookieName == NULL) || (apr_strnatcmp(cookieName, currentCookieName) != 0)) { oidc_proto_state_t *proto_state = oidc_proto_state_from_cookie(r, c, cookie); if (proto_state != NULL) { json_int_t ts = oidc_proto_state_get_timestamp( proto_state); if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) { oidc_error(r, "state (%s) has expired", cookieName); oidc_util_set_cookie(r, cookieName, "", 0, NULL); } oidc_proto_state_destroy(proto_state); } } } } cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); } } } /* * restore the state that was maintained between authorization request and response in an encrypted cookie */ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c, const char *state, oidc_proto_state_t **proto_state) { oidc_debug(r, "enter"); const char *cookieName = oidc_get_state_cookie_name(r, state); /* clean expired state cookies to avoid pollution */ oidc_clean_expired_state_cookies(r, c, cookieName); /* get the state cookie value first */ char *cookieValue = oidc_util_get_cookie(r, cookieName); if (cookieValue == NULL) { oidc_error(r, "no \"%s\" state cookie found", cookieName); return oidc_unsolicited_proto_state(r, c, state, proto_state); } /* clear state cookie because we don't need it anymore */ oidc_util_set_cookie(r, cookieName, "", 0, NULL); *proto_state = oidc_proto_state_from_cookie(r, c, cookieValue); if (*proto_state == NULL) return FALSE; const char *nonce = oidc_proto_state_get_nonce(*proto_state); /* calculate the hash of the browser fingerprint concatenated with the nonce */ char *calc = oidc_get_browser_state_hash(r, nonce); /* compare the calculated hash with the value provided in the authorization response */ if (apr_strnatcmp(calc, state) != 0) { oidc_error(r, "calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"", state, calc); oidc_proto_state_destroy(*proto_state); return FALSE; } apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state); /* check that the timestamp is not beyond the valid interval */ if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) { oidc_error(r, "state has expired"); oidc_util_html_send_error(r, c->error_template, "Invalid Authentication Response", apr_psprintf(r->pool, "This is due to a timeout; please restart your authentication session by re-entering the URL/bookmark you originally wanted to access: %s", oidc_proto_state_get_original_url(*proto_state)), DONE); oidc_proto_state_destroy(*proto_state); return FALSE; } /* add the state */ oidc_proto_state_set_state(*proto_state, state); /* log the restored state object */ oidc_debug(r, "restored state: %s", oidc_proto_state_to_string(r, *proto_state)); /* we've made it */ return TRUE; } /* * set the state that is maintained between an authorization request and an authorization response * in a cookie in the browser that is cryptographically bound to that state */ static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c, const char *state, oidc_proto_state_t *proto_state) { /* * create a cookie consisting of 8 elements: * random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp * encoded as JSON, encrypting the resulting JSON value */ char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state); if (cookieValue == NULL) return FALSE; /* clean expired state cookies to avoid pollution */ oidc_clean_expired_state_cookies(r, c, NULL); /* assemble the cookie name for the state cookie */ const char *cookieName = oidc_get_state_cookie_name(r, state); /* set it as a cookie */ oidc_util_set_cookie(r, cookieName, cookieValue, -1, c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : NULL); //free(s_value); return TRUE; } /* * get the mod_auth_openidc related context from the (userdata in the) request * (used for passing state between various Apache request processing stages and hook callbacks) */ static apr_table_t *oidc_request_state(request_rec *rr) { /* our state is always stored in the main request */ request_rec *r = (rr->main != NULL) ? rr->main : rr; /* our state is a table, get it */ apr_table_t *state = NULL; apr_pool_userdata_get((void **) &state, OIDC_USERDATA_KEY, r->pool); /* if it does not exist, we'll create a new table */ if (state == NULL) { state = apr_table_make(r->pool, 5); apr_pool_userdata_set(state, OIDC_USERDATA_KEY, NULL, r->pool); } /* return the resulting table, always non-null now */ return state; } /* * set a name/value pair in the mod_auth_openidc-specific request context * (used for passing state between various Apache request processing stages and hook callbacks) */ void oidc_request_state_set(request_rec *r, const char *key, const char *value) { /* get a handle to the global state, which is a table */ apr_table_t *state = oidc_request_state(r); /* put the name/value pair in that table */ apr_table_set(state, key, value); } /* * get a name/value pair from the mod_auth_openidc-specific request context * (used for passing state between various Apache request processing stages and hook callbacks) */ const char*oidc_request_state_get(request_rec *r, const char *key) { /* get a handle to the global state, which is a table */ apr_table_t *state = oidc_request_state(r); /* return the value from the table */ return apr_table_get(state, key); } /* * set the claims from a JSON object (c.q. id_token or user_info response) stored * in the session in to HTTP headers passed on to the application */ static apr_byte_t oidc_set_app_claims(request_rec *r, const oidc_cfg * const cfg, oidc_session_t *session, const char *s_claims) { json_t *j_claims = NULL; /* decode the string-encoded attributes in to a JSON structure */ if (s_claims != NULL) { if (oidc_util_decode_json_object(r, s_claims, &j_claims) == FALSE) return FALSE; } /* set the resolved claims a HTTP headers for the application */ if (j_claims != NULL) { oidc_util_set_app_infos(r, j_claims, oidc_cfg_claim_prefix(r), cfg->claim_delimiter, oidc_cfg_dir_pass_info_in_headers(r), oidc_cfg_dir_pass_info_in_envvars(r)); /* release resources */ json_decref(j_claims); } return TRUE; } static int oidc_authenticate_user(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *original_url, const char *login_hint, const char *id_token_hint, const char *prompt, const char *auth_request_params, const char *path_scope); /* * log message about max session duration */ static void oidc_log_session_expires(request_rec *r, const char *msg, apr_time_t session_expires) { char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, session_expires); oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf, apr_time_sec(session_expires - apr_time_now())); } /* * find out which action we need to take when encountering an unauthenticated request */ static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) { /* see if we've configured OIDCUnAuthAction for this path */ switch (oidc_dir_cfg_unauth_action(r)) { case OIDC_UNAUTH_RETURN410: return HTTP_GONE; case OIDC_UNAUTH_RETURN401: return HTTP_UNAUTHORIZED; case OIDC_UNAUTH_PASS: r->user = ""; /* * we're not going to pass information about an authenticated user to the application, * but we do need to scrub the headers that mod_auth_openidc would set for security reasons */ oidc_scrub_headers(r); return OK; case OIDC_UNAUTH_AUTHENTICATE: /* * exception handling: if this looks like a XMLHttpRequest call we * won't redirect the user and thus avoid creating a state cookie * for a non-browser (= Javascript) call that will never return from the OP */ if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL) && (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r), OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0)) return HTTP_UNAUTHORIZED; } /* * else: no session (regardless of whether it is main or sub-request), * and we need to authenticate the user */ return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); } /* * see if this is a non-browser request */ static apr_byte_t oidc_is_xml_http_request(request_rec *r) { if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL) && (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r), OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0)) return TRUE; return FALSE; } /* * check if maximum session duration was exceeded */ static int oidc_check_max_session_duration(request_rec *r, oidc_cfg *cfg, oidc_session_t *session) { /* get the session expiry from the session data */ apr_time_t session_expires = oidc_session_get_session_expires(r, session); /* check the expire timestamp against the current time */ if (apr_time_now() > session_expires) { oidc_warn(r, "maximum session duration exceeded for user: %s", session->remote_user); oidc_session_kill(r, session); return oidc_handle_unauthenticated_user(r, cfg); } /* log message about max session duration */ oidc_log_session_expires(r, "session max lifetime", session_expires); return OK; } /* * validate received session cookie against the domain it was issued for: * * this handles the case where the cache configured is a the same single memcache, Redis, or file * backend for different (virtual) hosts, or a client-side cookie protected with the same secret * * it also handles the case that a cookie is unexpectedly shared across multiple hosts in * name-based virtual hosting even though the OP(s) would be the same */ static apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg *cfg, oidc_session_t *session) { const char *c_cookie_domain = cfg->cookie_domain ? cfg->cookie_domain : oidc_get_current_url_host(r); const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session); if ((s_cookie_domain == NULL) || (apr_strnatcmp(c_cookie_domain, s_cookie_domain) != 0)) { oidc_warn(r, "aborting: detected attempt to play cookie against a different domain/host than issued for! (issued=%s, current=%s)", s_cookie_domain, c_cookie_domain); return FALSE; } return TRUE; } /* * get a handle to the provider configuration via the "issuer" stored in the session */ apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg *c, oidc_session_t *session, oidc_provider_t **provider) { oidc_debug(r, "enter"); /* get the issuer value from the session state */ const char *issuer = oidc_session_get_issuer(r, session); if (issuer == NULL) { oidc_error(r, "session corrupted: no issuer found in session"); return FALSE; } /* get the provider info associated with the issuer value */ oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE); if (p == NULL) { oidc_error(r, "session corrupted: no provider found for issuer: %s", issuer); return FALSE; } *provider = p; return TRUE; } /* * store claims resolved from the userinfo endpoint in the session */ static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *c, oidc_session_t *session, oidc_provider_t *provider, const char *claims, const char *userinfo_jwt) { oidc_debug(r, "enter"); /* see if we've resolved any claims */ if (claims != NULL) { /* * Successfully decoded a set claims from the response so we can store them * (well actually the stringified representation in the response) * in the session context safely now */ oidc_session_set_userinfo_claims(r, session, claims); if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* this will also clear the entry if a JWT was not returned at this point */ oidc_session_set_userinfo_jwt(r, session, userinfo_jwt); } } else { /* * clear the existing claims because we could not refresh them */ oidc_session_set_userinfo_claims(r, session, NULL); oidc_session_set_userinfo_jwt(r, session, NULL); } /* store the last refresh time if we've configured a userinfo refresh interval */ if (provider->userinfo_refresh_interval > 0) oidc_session_reset_userinfo_last_refresh(r, session); } /* * execute refresh token grant to refresh the existing access token */ static apr_byte_t oidc_refresh_access_token(request_rec *r, oidc_cfg *c, oidc_session_t *session, oidc_provider_t *provider, char **new_access_token) { oidc_debug(r, "enter"); /* get the refresh token that was stored in the session */ const char *refresh_token = oidc_session_get_refresh_token(r, session); if (refresh_token == NULL) { oidc_warn(r, "refresh token routine called but no refresh_token found in the session"); return FALSE; } /* elements returned in the refresh response */ char *s_id_token = NULL; int expires_in = -1; char *s_token_type = NULL; char *s_access_token = NULL; char *s_refresh_token = NULL; /* refresh the tokens by calling the token endpoint */ if (oidc_proto_refresh_request(r, c, provider, refresh_token, &s_id_token, &s_access_token, &s_token_type, &expires_in, &s_refresh_token) == FALSE) { oidc_error(r, "access_token could not be refreshed"); return FALSE; } /* store the new access_token in the session and discard the old one */ oidc_session_set_access_token(r, session, s_access_token); oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_reset_access_token_last_refresh(r, session); /* see if we need to return it as a parameter */ if (new_access_token != NULL) *new_access_token = s_access_token; /* if we have a new refresh token (rolling refresh), store it in the session and overwrite the old one */ if (s_refresh_token != NULL) oidc_session_set_refresh_token(r, session, s_refresh_token); return TRUE; } /* * retrieve claims from the userinfo endpoint and return the stringified response */ static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *access_token, oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) { oidc_debug(r, "enter"); /* see if a userinfo endpoint is set, otherwise there's nothing to do for us */ if (provider->userinfo_endpoint_url == NULL) { oidc_debug(r, "not retrieving userinfo claims because userinfo_endpoint is not set"); return NULL; } /* see if there's an access token, otherwise we can't call the userinfo endpoint at all */ if (access_token == NULL) { oidc_debug(r, "not retrieving userinfo claims because access_token is not provided"); return NULL; } if ((id_token_sub == NULL) && (session != NULL)) { // when refreshing claims from the userinfo endpoint json_t *id_token_claims = oidc_session_get_idtoken_claims_json(r, session); if (id_token_claims == NULL) { oidc_error(r, "no id_token_claims found in session"); return NULL; } oidc_jose_get_string(r->pool, id_token_claims, OIDC_CLAIM_SUB, FALSE, &id_token_sub, NULL); } // TODO: return code should indicate whether the token expired or some other error occurred // TODO: long-term: session storage should be JSON (with explicit types and less conversion, using standard routines) /* try to get claims from the userinfo endpoint using the provided access token */ char *result = NULL; if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, access_token, &result, userinfo_jwt) == FALSE) { /* see if we have an existing session and we are refreshing the user info claims */ if (session != NULL) { /* first call to user info endpoint failed, but the access token may have just expired, so refresh it */ char *access_token = NULL; if (oidc_refresh_access_token(r, c, session, provider, &access_token) == TRUE) { /* try again with the new access token */ if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, access_token, &result, userinfo_jwt) == FALSE) { oidc_error(r, "resolving user info claims with the refreshed access token failed, nothing will be stored in the session"); result = NULL; } } else { oidc_warn(r, "refreshing access token failed, claims will not be retrieved/refreshed from the userinfo endpoint"); result = NULL; } } else { oidc_error(r, "resolving user info claims with the existing/provided access token failed, nothing will be stored in the session"); result = NULL; } } return result; } /* * get (new) claims from the userinfo endpoint */ static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r, oidc_cfg *cfg, oidc_session_t *session) { oidc_provider_t *provider = NULL; const char *claims = NULL; const char *access_token = NULL; char *userinfo_jwt = NULL; /* get the current provider info */ if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) return FALSE; /* see if we can do anything here, i.e. we have a userinfo endpoint and a refresh interval is configured */ apr_time_t interval = apr_time_from_sec( provider->userinfo_refresh_interval); oidc_debug(r, "userinfo_endpoint=%s, interval=%d", provider->userinfo_endpoint_url, provider->userinfo_refresh_interval); if ((provider->userinfo_endpoint_url != NULL) && (interval > 0)) { /* get the last refresh timestamp from the session info */ apr_time_t last_refresh = oidc_session_get_userinfo_last_refresh(r, session); oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds", apr_time_sec(last_refresh + interval - apr_time_now())); /* see if we need to refresh again */ if (last_refresh + interval < apr_time_now()) { /* get the current access token */ access_token = oidc_session_get_access_token(r, session); /* retrieve the current claims */ claims = oidc_retrieve_claims_from_userinfo_endpoint(r, cfg, provider, access_token, session, NULL, &userinfo_jwt); /* store claims resolved from userinfo endpoint */ oidc_store_userinfo_claims(r, cfg, session, provider, claims, userinfo_jwt); /* indicated something changed */ return TRUE; } } return FALSE; } /* * copy the claims and id_token from the session to the request state and optionally return them */ static void oidc_copy_tokens_to_request_state(request_rec *r, oidc_session_t *session, const char **s_id_token, const char **s_claims) { const char *id_token = oidc_session_get_idtoken_claims(r, session); const char *claims = oidc_session_get_userinfo_claims(r, session); oidc_debug(r, "id_token=%s claims=%s", id_token, claims); if (id_token != NULL) { oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token); if (s_id_token != NULL) *s_id_token = id_token; } if (claims != NULL) { oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims); if (s_claims != NULL) *s_claims = claims; } } /* * pass refresh_token, access_token and access_token_expires as headers/environment variables to the application */ static apr_byte_t oidc_session_pass_tokens_and_save(request_rec *r, oidc_cfg *cfg, oidc_session_t *session, apr_byte_t needs_save) { apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); /* set the refresh_token in the app headers/variables, if enabled for this location/directory */ const char *refresh_token = oidc_session_get_refresh_token(r, session); if ((oidc_cfg_dir_pass_refresh_token(r) != 0) && (refresh_token != NULL)) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_REFRESH_TOKEN, refresh_token, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } /* set the access_token in the app headers/variables */ const char *access_token = oidc_session_get_access_token(r, session); if (access_token != NULL) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } /* set the expiry timestamp in the app headers/variables */ const char *access_token_expires = oidc_session_get_access_token_expires(r, session); if (access_token_expires != NULL) { /* pass it to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_EXP, access_token_expires, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } /* * reset the session inactivity timer * but only do this once per 10% of the inactivity timeout interval (with a max to 60 seconds) * for performance reasons * * now there's a small chance that the session ends 10% (or a minute) earlier than configured/expected * cq. when there's a request after a recent save (so no update) and then no activity happens until * a request comes in just before the session should expire * ("recent" and "just before" refer to 10%-with-a-max-of-60-seconds of the inactivity interval after * the start/last-update and before the expiry of the session respectively) * * this is be deemed acceptable here because of performance gain */ apr_time_t interval = apr_time_from_sec(cfg->session_inactivity_timeout); apr_time_t now = apr_time_now(); apr_time_t slack = interval / 10; if (slack > apr_time_from_sec(60)) slack = apr_time_from_sec(60); if (session->expiry - now < interval - slack) { session->expiry = now + interval; needs_save = TRUE; } /* log message about session expiry */ oidc_log_session_expires(r, "session inactivity timeout", session->expiry); /* check if something was updated in the session and we need to save it again */ if (needs_save) if (oidc_session_save(r, session, FALSE) == FALSE) return FALSE; return TRUE; } /* * handle the case where we have identified an existing authentication session for a user */ static int oidc_handle_existing_session(request_rec *r, oidc_cfg *cfg, oidc_session_t *session) { oidc_debug(r, "enter"); /* set the user in the main request for further (incl. sub-request) processing */ r->user = (char *) session->remote_user; oidc_debug(r, "set remote_user to \"%s\"", r->user); /* get the header name in which the remote user name needs to be passed */ char *authn_header = oidc_cfg_dir_authn_header(r); apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); /* verify current cookie domain against issued cookie domain */ if (oidc_check_cookie_domain(r, cfg, session) == FALSE) return HTTP_UNAUTHORIZED; /* check if the maximum session duration was exceeded */ int rc = oidc_check_max_session_duration(r, cfg, session); if (rc != OK) return rc; /* if needed, refresh claims from the user info endpoint */ apr_byte_t needs_save = oidc_refresh_claims_from_userinfo_endpoint(r, cfg, session); /* * we're going to pass the information that we have to the application, * but first we need to scrub the headers that we're going to use for security reasons */ oidc_scrub_headers(r); /* set the user authentication HTTP header if set and required */ if ((r->user != NULL) && (authn_header != NULL)) oidc_util_hdr_in_set(r, authn_header, r->user); const char *s_claims = NULL; const char *s_id_token = NULL; /* copy id_token and claims from session to request state and obtain their values */ oidc_copy_tokens_to_request_state(r, session, &s_id_token, &s_claims); if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_CLAIMS)) { /* set the userinfo claims in the app headers */ if (oidc_set_app_claims(r, cfg, session, s_claims) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JSON_OBJECT)) { /* pass the userinfo JSON object to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JSON, s_claims, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JWT)) { if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* get the compact serialized JWT from the session */ const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r, session); if (s_userinfo_jwt != NULL) { /* pass the compact serialized JWT to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JWT, s_userinfo_jwt, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } else { oidc_debug(r, "configured to pass userinfo in a JWT, but no such JWT was found in the session (probably no such JWT was returned from the userinfo endpoint)"); } } else { oidc_error(r, "session type \"client-cookie\" does not allow storing/passing a userinfo JWT; use \"" OIDCSessionType " server-cache\" for that"); } } if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) { /* set the id_token in the app headers */ if (oidc_set_app_claims(r, cfg, session, s_id_token) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) { /* pass the id_token JSON object to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN_PAYLOAD, s_id_token, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) { if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* get the compact serialized JWT from the session */ const char *s_id_token = oidc_session_get_idtoken(r, session); /* pass the compact serialized JWT to the app in a header or environment variable */ oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN, s_id_token, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } else { oidc_error(r, "session type \"client-cookie\" does not allow storing/passing the id_token; use \"" OIDCSessionType " server-cache\" for that"); } } /* pass the at, rt and at expiry to the application, possibly update the session expiry and save the session */ if (oidc_session_pass_tokens_and_save(r, cfg, session, needs_save) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* return "user authenticated" status */ return OK; } /* * helper function for basic/implicit client flows upon receiving an authorization response: * check that it matches the state stored in the browser and return the variables associated * with the state, such as original_url and OP oidc_provider_t pointer. */ static apr_byte_t oidc_authorization_response_match_state(request_rec *r, oidc_cfg *c, const char *state, struct oidc_provider_t **provider, oidc_proto_state_t **proto_state) { oidc_debug(r, "enter (state=%s)", state); if ((state == NULL) || (apr_strnatcmp(state, "") == 0)) { oidc_error(r, "state parameter is not set"); return FALSE; } /* check the state parameter against what we stored in a cookie */ if (oidc_restore_proto_state(r, c, state, proto_state) == FALSE) { oidc_error(r, "unable to restore state"); return FALSE; } *provider = oidc_get_provider_for_issuer(r, c, oidc_proto_state_get_issuer(*proto_state), FALSE); return (*provider != NULL); } /* * redirect the browser to the session logout endpoint */ static int oidc_session_redirect_parent_window_to_logout(request_rec *r, oidc_cfg *c) { oidc_debug(r, "enter"); char *java_script = apr_psprintf(r->pool, " \n", oidc_get_redirect_uri(r, c)); return oidc_util_html_send(r, "Redirecting...", java_script, NULL, NULL, DONE); } /* * handle an error returned by the OP */ static int oidc_authorization_response_error(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, const char *error, const char *error_description) { const char *prompt = oidc_proto_state_get_prompt(proto_state); if (prompt != NULL) prompt = apr_pstrdup(r->pool, prompt); oidc_proto_state_destroy(proto_state); if ((prompt != NULL) && (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) { return oidc_session_redirect_parent_window_to_logout(r, c); } return oidc_util_html_send_error(r, c->error_template, apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error), error_description, DONE); } /* * get the r->user for this request based on the configuration for OIDC/OAuth */ apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *reg_exp, const char *replace, json_t *json, char **request_user) { /* get the claim value from the JSON object */ json_t *username = json_object_get(json, claim_name); if ((username == NULL) || (!json_is_string(username))) { oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name); return FALSE; } *request_user = apr_pstrdup(r->pool, json_string_value(username)); if (reg_exp != NULL) { char *error_str = NULL; if (replace == NULL) { if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp, request_user, &error_str) == FALSE) { oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str); *request_user = NULL; return FALSE; } } else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp, replace, request_user, &error_str) == FALSE) { oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str); *request_user = NULL; return FALSE; } } return TRUE; } /* * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables */ static apr_byte_t oidc_set_request_user(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *s_claims) { char *issuer = provider->issuer; char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim.claim_name); int n = strlen(claim_name); apr_byte_t post_fix_with_issuer = (claim_name[n - 1] == OIDC_CHAR_AT); if (post_fix_with_issuer == TRUE) { claim_name[n - 1] = '\0'; issuer = (strstr(issuer, "https://") == NULL) ? apr_pstrdup(r->pool, issuer) : apr_pstrdup(r->pool, issuer + strlen("https://")); } /* extract the username claim (default: "sub") from the id_token payload or user claims */ apr_byte_t rc = FALSE; char *remote_user = NULL; json_t *claims = NULL; oidc_util_decode_json_object(r, s_claims, &claims); if (claims == NULL) { rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp, c->remote_user_claim.replace, jwt->payload.value.json, &remote_user); } else { oidc_util_json_merge(r, jwt->payload.value.json, claims); rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp, c->remote_user_claim.replace, claims, &remote_user); json_decref(claims); } if ((rc == FALSE) || (remote_user == NULL)) { oidc_error(r, "" OIDCRemoteUserClaim "is set to \"%s\", but could not set the remote user based on the requested claim \"%s\" and the available claims for the user", c->remote_user_claim.claim_name, claim_name); return FALSE; } if (post_fix_with_issuer == TRUE) remote_user = apr_psprintf(r->pool, "%s%s%s", remote_user, OIDC_STR_AT, issuer); r->user = remote_user; oidc_debug(r, "set remote_user to \"%s\" based on claim: \"%s\"%s", r->user, c->remote_user_claim.claim_name, c->remote_user_claim.reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", c->remote_user_claim.reg_exp, c->remote_user_claim.replace) : ""); return TRUE; } /* * store resolved information in the session */ static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *c, oidc_session_t *session, oidc_provider_t *provider, const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token, const int expires_in, const char *refresh_token, const char *session_state, const char *state, const char *original_url, const char *userinfo_jwt) { /* store the user in the session */ session->remote_user = remoteUser; /* set the session expiry to the inactivity timeout */ session->expiry = apr_time_now() + apr_time_from_sec(c->session_inactivity_timeout); /* store the claims payload in the id_token for later reference */ oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.str); if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) { /* store the compact serialized representation of the id_token for later reference */ oidc_session_set_idtoken(r, session, id_token); } /* store the issuer in the session (at least needed for session mgmt and token refresh */ oidc_session_set_issuer(r, session, provider->issuer); /* store the state and original URL in the session for handling browser-back more elegantly */ oidc_session_set_request_state(r, session, state); oidc_session_set_original_url(r, session, original_url); if ((session_state != NULL) && (provider->check_session_iframe != NULL)) { /* store the session state and required parameters session management */ oidc_session_set_session_state(r, session, session_state); oidc_session_set_check_session_iframe(r, session, provider->check_session_iframe); oidc_session_set_client_id(r, session, provider->client_id); oidc_debug(r, "session management enabled: stored session_state (%s), check_session_iframe (%s) and client_id (%s) in the session", session_state, provider->check_session_iframe, provider->client_id); } else if (provider->check_session_iframe == NULL) { oidc_debug(r, "session management disabled: \"check_session_iframe\" is not set in provider configuration"); } else { oidc_warn(r, "session management disabled: no \"session_state\" value is provided in the authentication response even though \"check_session_iframe\" (%s) is set in the provider configuration", provider->check_session_iframe); } if (provider->end_session_endpoint != NULL) oidc_session_set_logout_endpoint(r, session, provider->end_session_endpoint); /* store claims resolved from userinfo endpoint */ oidc_store_userinfo_claims(r, c, session, provider, claims, userinfo_jwt); /* see if we have an access_token */ if (access_token != NULL) { /* store the access_token in the session context */ oidc_session_set_access_token(r, session, access_token); /* store the associated expires_in value */ oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ oidc_session_reset_access_token_last_refresh(r, session); } /* see if we have a refresh_token */ if (refresh_token != NULL) { /* store the refresh_token in the session context */ oidc_session_set_refresh_token(r, session, refresh_token); } /* store max session duration in the session as a hard cut-off expiry timestamp */ apr_time_t session_expires = (provider->session_max_duration == 0) ? apr_time_from_sec(id_token_jwt->payload.exp) : (apr_time_now() + apr_time_from_sec(provider->session_max_duration)); oidc_session_set_session_expires(r, session, session_expires); oidc_debug(r, "provider->session_max_duration = %d, session_expires=%" APR_TIME_T_FMT, provider->session_max_duration, session_expires); /* log message about max session duration */ oidc_log_session_expires(r, "session max lifetime", session_expires); /* store the domain for which this session is valid */ oidc_session_set_cookie_domain(r, session, c->cookie_domain ? c->cookie_domain : oidc_get_current_url_host(r)); /* store the session */ return oidc_session_save(r, session, TRUE); } /* * parse the expiry for the access token */ static int oidc_parse_expires_in(request_rec *r, const char *expires_in) { if (expires_in != NULL) { char *ptr = NULL; long number = strtol(expires_in, &ptr, 10); if (number <= 0) { oidc_warn(r, "could not convert \"expires_in\" value (%s) to a number", expires_in); return -1; } return number; } return -1; } /* * handle the different flows (hybrid, implicit, Authorization Code) */ static apr_byte_t oidc_handle_flows(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { apr_byte_t rc = FALSE; const char *requested_response_type = oidc_proto_state_get_response_type( proto_state); /* handle the requested response type/mode */ if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN)) { rc = oidc_proto_authorization_response_code_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN)) { rc = oidc_proto_authorization_response_code_idtoken(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN)) { rc = oidc_proto_handle_authorization_response_code_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE)) { rc = oidc_proto_handle_authorization_response_code(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN)) { rc = oidc_proto_handle_authorization_response_idtoken_token(r, c, proto_state, provider, params, response_mode, jwt); } else if (oidc_util_spaced_string_equals(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { rc = oidc_proto_handle_authorization_response_idtoken(r, c, proto_state, provider, params, response_mode, jwt); } else { oidc_error(r, "unsupported response type: \"%s\"", requested_response_type); } if ((rc == FALSE) && (*jwt != NULL)) { oidc_jwt_destroy(*jwt); *jwt = NULL; } return rc; } /* handle the browser back on an authorization response */ static apr_byte_t oidc_handle_browser_back(request_rec *r, const char *r_state, oidc_session_t *session) { /* see if we have an existing session and browser-back was used */ const char *s_state = NULL, *o_url = NULL; if (session->remote_user != NULL) { s_state = oidc_session_get_request_state(r, session); o_url = oidc_session_get_original_url(r, session); if ((r_state != NULL) && (s_state != NULL) && (apr_strnatcmp(r_state, s_state) == 0)) { /* log the browser back event detection */ oidc_warn(r, "browser back detected, redirecting to original URL: %s", o_url); /* go back to the URL that he originally tried to access */ oidc_util_hdr_out_location_set(r, o_url); return TRUE; } } return FALSE; } /* * complete the handling of an authorization response by obtaining, parsing and verifying the * id_token and storing the authenticated user state in the session */ static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c, oidc_session_t *session, apr_table_t *params, const char *response_mode) { oidc_debug(r, "enter, response_mode=%s", response_mode); oidc_provider_t *provider = NULL; oidc_proto_state_t *proto_state = NULL; oidc_jwt_t *jwt = NULL; /* see if this response came from a browser-back event */ if (oidc_handle_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE), session) == TRUE) return HTTP_MOVED_TEMPORARILY; /* match the returned state parameter against the state stored in the browser */ if (oidc_authorization_response_match_state(r, c, apr_table_get(params, OIDC_PROTO_STATE), &provider, &proto_state) == FALSE) { if (c->default_sso_url != NULL) { oidc_warn(r, "invalid authorization response state; a default SSO URL is set, sending the user there: %s", c->default_sso_url); oidc_util_hdr_out_location_set(r, c->default_sso_url); return HTTP_MOVED_TEMPORARILY; } oidc_error(r, "invalid authorization response state and no default SSO URL is set, sending an error..."); return HTTP_INTERNAL_SERVER_ERROR; } /* see if the response is an error response */ if (apr_table_get(params, OIDC_PROTO_ERROR) != NULL) return oidc_authorization_response_error(r, c, proto_state, apr_table_get(params, OIDC_PROTO_ERROR), apr_table_get(params, OIDC_PROTO_ERROR_DESCRIPTION)); /* handle the code, implicit or hybrid flow */ if (oidc_handle_flows(r, c, proto_state, provider, params, response_mode, &jwt) == FALSE) return oidc_authorization_response_error(r, c, proto_state, "Error in handling response type.", NULL); if (jwt == NULL) { oidc_error(r, "no id_token was provided"); return oidc_authorization_response_error(r, c, proto_state, "No id_token was provided.", NULL); } int expires_in = oidc_parse_expires_in(r, apr_table_get(params, OIDC_PROTO_EXPIRES_IN)); char *userinfo_jwt = NULL; /* * optionally resolve additional claims against the userinfo endpoint * parsed claims are not actually used here but need to be parsed anyway for error checking purposes */ const char *claims = oidc_retrieve_claims_from_userinfo_endpoint(r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL, jwt->payload.sub, &userinfo_jwt); /* restore the original protected URL that the user was trying to access */ const char *original_url = oidc_proto_state_get_original_url(proto_state); if (original_url != NULL) original_url = apr_pstrdup(r->pool, original_url); const char *original_method = oidc_proto_state_get_original_method( proto_state); if (original_method != NULL) original_method = apr_pstrdup(r->pool, original_method); const char *prompt = oidc_proto_state_get_prompt(proto_state); /* set the user */ if (oidc_set_request_user(r, c, provider, jwt, claims) == TRUE) { /* session management: if the user in the new response is not equal to the old one, error out */ if ((prompt != NULL) && (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) { // TOOD: actually need to compare sub? (need to store it in the session separately then //const char *sub = NULL; //oidc_session_get(r, session, "sub", &sub); //if (apr_strnatcmp(sub, jwt->payload.sub) != 0) { if (apr_strnatcmp(session->remote_user, r->user) != 0) { oidc_warn(r, "user set from new id_token is different from current one"); oidc_jwt_destroy(jwt); return oidc_authorization_response_error(r, c, proto_state, "User changed!", NULL); } } /* store resolved information in the session */ if (oidc_save_in_session(r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), expires_in, apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN), apr_table_get(params, OIDC_PROTO_SESSION_STATE), apr_table_get(params, OIDC_PROTO_STATE), original_url, userinfo_jwt) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } else { oidc_error(r, "remote user could not be set"); return oidc_authorization_response_error(r, c, proto_state, "Remote user could not be set: contact the website administrator", NULL); } /* cleanup */ oidc_proto_state_destroy(proto_state); oidc_jwt_destroy(jwt); /* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */ if (r->user == NULL) return HTTP_UNAUTHORIZED; /* log the successful response */ oidc_debug(r, "session created and stored, returning to original URL: %s, original method: %s", original_url, original_method); /* check whether form post data was preserved; if so restore it */ if (apr_strnatcmp(original_method, OIDC_METHOD_FORM_POST) == 0) { return oidc_request_post_preserved_restore(r, original_url); } /* now we've authenticated the user so go back to the URL that he originally tried to access */ oidc_util_hdr_out_location_set(r, original_url); /* do the actual redirect to the original URL */ return HTTP_MOVED_TEMPORARILY; } /* * handle an OpenID Connect Authorization Response using the POST (+fragment->POST) response_mode */ static int oidc_handle_post_authorization_response(request_rec *r, oidc_cfg *c, oidc_session_t *session) { oidc_debug(r, "enter"); /* initialize local variables */ char *response_mode = NULL; /* read the parameters that are POST-ed to us */ apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params) == FALSE) { oidc_error(r, "something went wrong when reading the POST parameters"); return HTTP_INTERNAL_SERVER_ERROR; } /* see if we've got any POST-ed data at all */ if ((apr_table_elts(params)->nelts < 1) || ((apr_table_elts(params)->nelts == 1) && apr_table_get(params, OIDC_PROTO_RESPONSE_MODE) && (apr_strnatcmp( apr_table_get(params, OIDC_PROTO_RESPONSE_MODE), OIDC_PROTO_RESPONSE_MODE_FRAGMENT) == 0))) { return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "You've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you should not open this URL in your browser directly, or have the server administrator use a different " OIDCRedirectURI " setting.", HTTP_INTERNAL_SERVER_ERROR); } /* get the parameters */ response_mode = (char *) apr_table_get(params, OIDC_PROTO_RESPONSE_MODE); /* do the actual implicit work */ return oidc_handle_authorization_response(r, c, session, params, response_mode ? response_mode : OIDC_PROTO_RESPONSE_MODE_FORM_POST); } /* * handle an OpenID Connect Authorization Response using the redirect response_mode */ static int oidc_handle_redirect_authorization_response(request_rec *r, oidc_cfg *c, oidc_session_t *session) { oidc_debug(r, "enter"); /* read the parameters from the query string */ apr_table_t *params = apr_table_make(r->pool, 8); oidc_util_read_form_encoded_params(r, params, r->args); /* do the actual work */ return oidc_handle_authorization_response(r, c, session, params, OIDC_PROTO_RESPONSE_MODE_QUERY); } /* * present the user with an OP selection screen */ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) { oidc_debug(r, "enter"); /* obtain the URL we're currently accessing, to be stored in the state/session */ char *current_url = oidc_get_current_url(r); const char *method = oidc_original_request_method(r, cfg, FALSE); /* generate CSRF token */ char *csrf = NULL; if (oidc_proto_generate_nonce(r, &csrf, 8) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; char *path_scopes = oidc_dir_cfg_path_scope(r); char *path_auth_request_params = oidc_dir_cfg_path_auth_request_params(r); char *discover_url = oidc_cfg_dir_discover_url(r); /* see if there's an external discovery page configured */ if (discover_url != NULL) { /* yes, assemble the parameters for external discovery */ char *url = apr_psprintf(r->pool, "%s%s%s=%s&%s=%s&%s=%s&%s=%s", discover_url, strchr(discover_url, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url), OIDC_DISC_RM_PARAM, method, OIDC_DISC_CB_PARAM, oidc_util_escape_string(r, oidc_get_redirect_uri(r, cfg)), OIDC_CSRF_NAME, oidc_util_escape_string(r, csrf)); if (path_scopes != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_SC_PARAM, oidc_util_escape_string(r, path_scopes)); if (path_auth_request_params != NULL) url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_AR_PARAM, oidc_util_escape_string(r, path_auth_request_params)); /* log what we're about to do */ oidc_debug(r, "redirecting to external discovery page: %s", url); /* set CSRF cookie */ oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, cfg->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : NULL); /* see if we need to preserve POST parameters through Javascript/HTML5 storage */ if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE) return DONE; /* do the actual redirect to an external discovery page */ oidc_util_hdr_out_location_set(r, url); return HTTP_MOVED_TEMPORARILY; } /* get a list of all providers configured in the metadata directory */ apr_array_header_t *arr = NULL; if (oidc_metadata_list(r, cfg, &arr) == FALSE) return oidc_util_html_send_error(r, cfg->error_template, "Configuration Error", "No configured providers found, contact your administrator", HTTP_UNAUTHORIZED); /* assemble a where-are-you-from IDP discovery HTML page */ const char *s = "

Select your OpenID Connect Identity Provider

\n"; /* list all configured providers in there */ int i; for (i = 0; i < arr->nelts; i++) { const char *issuer = ((const char**) arr->elts)[i]; // TODO: html escape (especially & character) char *href = apr_psprintf(r->pool, "%s?%s=%s&%s=%s&%s=%s&%s=%s", oidc_get_redirect_uri(r, cfg), OIDC_DISC_OP_PARAM, oidc_util_escape_string(r, issuer), OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url), OIDC_DISC_RM_PARAM, method, OIDC_CSRF_NAME, csrf); if (path_scopes != NULL) href = apr_psprintf(r->pool, "%s&%s=%s", href, OIDC_DISC_SC_PARAM, oidc_util_escape_string(r, path_scopes)); if (path_auth_request_params != NULL) href = apr_psprintf(r->pool, "%s&%s=%s", href, OIDC_DISC_AR_PARAM, oidc_util_escape_string(r, path_auth_request_params)); char *display = (strstr(issuer, "https://") == NULL) ? apr_pstrdup(r->pool, issuer) : apr_pstrdup(r->pool, issuer + strlen("https://")); /* strip port number */ //char *p = strstr(display, ":"); //if (p != NULL) *p = '\0'; /* point back to the redirect_uri, where the selection is handled, with an IDP selection and return_to URL */ s = apr_psprintf(r->pool, "%s

%s

\n", s, href, display); } /* add an option to enter an account or issuer name for dynamic OP discovery */ s = apr_psprintf(r->pool, "%s
\n", s, oidc_get_redirect_uri(r, cfg)); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_RT_PARAM, current_url); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_RM_PARAM, method); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_CSRF_NAME, csrf); if (path_scopes != NULL) s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_SC_PARAM, path_scopes); if (path_auth_request_params != NULL) s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_AR_PARAM, path_auth_request_params); s = apr_psprintf(r->pool, "%s

Or enter your account name (eg. "mike@seed.gluu.org", or an IDP identifier (eg. "mitreid.org"):

\n", s); s = apr_psprintf(r->pool, "%s

\n", s, OIDC_DISC_OP_PARAM, ""); s = apr_psprintf(r->pool, "%s

\n", s); s = apr_psprintf(r->pool, "%s
\n", s); oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1, cfg->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : NULL); char *javascript = NULL, *javascript_method = NULL; char *html_head = ""; if (oidc_post_preserve_javascript(r, NULL, &javascript, &javascript_method) == TRUE) html_head = apr_psprintf(r->pool, "%s%s", html_head, javascript); /* now send the HTML contents to the user agent */ return oidc_util_html_send(r, "OpenID Connect Provider Discovery", html_head, javascript_method, s, DONE); } /* * authenticate the user to the selected OP, if the OP is not selected yet perform discovery first */ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *original_url, const char *login_hint, const char *id_token_hint, const char *prompt, const char *auth_request_params, const char *path_scope) { oidc_debug(r, "enter"); if (provider == NULL) { // TODO: should we use an explicit redirect to the discovery endpoint (maybe a "discovery" param to the redirect_uri)? if (c->metadata_dir != NULL) return oidc_discovery(r, c); /* we're not using multiple OP's configured in a metadata directory, pick the statically configured OP */ if (oidc_provider_static_config(r, c, &provider) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } /* generate the random nonce value that correlates requests and responses */ char *nonce = NULL; if (oidc_proto_generate_nonce(r, &nonce, OIDC_PROTO_NONCE_LENGTH) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; char *pkce_state = NULL; char *code_challenge = NULL; if ((oidc_util_spaced_string_contains(r->pool, provider->response_type, OIDC_PROTO_CODE) == TRUE) && (provider->pkce != NULL)) { /* generate the code verifier value that correlates authorization requests and code exchange requests */ if (provider->pkce->state(r, &pkce_state) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* generate the PKCE code challenge */ if (provider->pkce->challenge(r, pkce_state, &code_challenge) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; } /* create the state between request/response */ oidc_proto_state_t *proto_state = oidc_proto_state_new(); oidc_proto_state_set_original_url(proto_state, original_url); oidc_proto_state_set_original_method(proto_state, oidc_original_request_method(r, c, TRUE)); oidc_proto_state_set_issuer(proto_state, provider->issuer); oidc_proto_state_set_response_type(proto_state, provider->response_type); oidc_proto_state_set_nonce(proto_state, nonce); oidc_proto_state_set_timestamp_now(proto_state); if (provider->response_mode) oidc_proto_state_set_response_mode(proto_state, provider->response_mode); if (prompt) oidc_proto_state_set_prompt(proto_state, prompt); if (pkce_state) oidc_proto_state_set_pkce_state(proto_state, pkce_state); /* get a hash value that fingerprints the browser concatenated with the random input */ char *state = oidc_get_browser_state_hash(r, nonce); /* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */ if (oidc_authorization_request_set_cookie(r, c, state, proto_state) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* * printout errors if Cookie settings are not going to work */ apr_uri_t o_uri; memset(&o_uri, 0, sizeof(apr_uri_t)); apr_uri_t r_uri; memset(&r_uri, 0, sizeof(apr_uri_t)); apr_uri_parse(r->pool, original_url, &o_uri); apr_uri_parse(r->pool, oidc_get_redirect_uri(r, c), &r_uri); if ((apr_strnatcmp(o_uri.scheme, r_uri.scheme) != 0) && (apr_strnatcmp(r_uri.scheme, "https") == 0)) { oidc_error(r, "the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!", r_uri.scheme, o_uri.scheme); return HTTP_INTERNAL_SERVER_ERROR; } if (c->cookie_domain == NULL) { if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) { char *p = strstr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) { oidc_error(r, "the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!", r_uri.hostname, o_uri.hostname); return HTTP_INTERNAL_SERVER_ERROR; } } } else { if (!oidc_util_cookie_domain_valid(r_uri.hostname, c->cookie_domain)) { oidc_error(r, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!", c->cookie_domain, o_uri.hostname, original_url); return HTTP_INTERNAL_SERVER_ERROR; } } /* send off to the OpenID Connect Provider */ // TODO: maybe show intermediate/progress screen "redirecting to" return oidc_proto_authorization_request(r, provider, login_hint, oidc_get_redirect_uri_iss(r, c, provider), state, proto_state, id_token_hint, code_challenge, auth_request_params, path_scope); } /* * check if the target_link_uri matches to configuration settings to prevent an open redirect */ static int oidc_target_link_uri_matches_configuration(request_rec *r, oidc_cfg *cfg, const char *target_link_uri) { apr_uri_t o_uri; apr_uri_parse(r->pool, target_link_uri, &o_uri); if (o_uri.hostname == NULL) { oidc_error(r, "could not parse the \"target_link_uri\" (%s) in to a valid URL: aborting.", target_link_uri); return FALSE; } apr_uri_t r_uri; apr_uri_parse(r->pool, oidc_get_redirect_uri(r, cfg), &r_uri); if (cfg->cookie_domain == NULL) { /* cookie_domain set: see if the target_link_uri matches the redirect_uri host (because the session cookie will be set host-wide) */ if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) { char *p = strstr(o_uri.hostname, r_uri.hostname); if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) { oidc_error(r, "the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the \"target_link_uri\" (%s): aborting to prevent an open redirect.", r_uri.hostname, o_uri.hostname); return FALSE; } } } else { /* cookie_domain set: see if the target_link_uri is within the cookie_domain */ char *p = strstr(o_uri.hostname, cfg->cookie_domain); if ((p == NULL) || (apr_strnatcmp(cfg->cookie_domain, p) != 0)) { oidc_error(r, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.", cfg->cookie_domain, o_uri.hostname, target_link_uri); return FALSE; } } /* see if the cookie_path setting matches the target_link_uri path */ char *cookie_path = oidc_cfg_dir_cookie_path(r); if (cookie_path != NULL) { char *p = (o_uri.path != NULL) ? strstr(o_uri.path, cookie_path) : NULL; if ((p == NULL) || (p != o_uri.path)) { oidc_error(r, "the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.", cfg->cookie_domain, o_uri.path, target_link_uri); return FALSE; } else if (strlen(o_uri.path) > strlen(cookie_path)) { int n = strlen(cookie_path); if (cookie_path[n - 1] == OIDC_CHAR_FORWARD_SLASH) n--; if (o_uri.path[n] != OIDC_CHAR_FORWARD_SLASH) { oidc_error(r, "the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.", cfg->cookie_domain, o_uri.path, target_link_uri); return FALSE; } } } return TRUE; } /* * handle a response from an IDP discovery page and/or handle 3rd-party initiated SSO */ static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) { /* variables to hold the values returned in the response */ char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL, *auth_request_params = NULL, *csrf_cookie, *csrf_query = NULL, *user = NULL, *path_scopes; oidc_provider_t *provider = NULL; oidc_util_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer); oidc_util_get_request_parameter(r, OIDC_DISC_USER_PARAM, &user); oidc_util_get_request_parameter(r, OIDC_DISC_RT_PARAM, &target_link_uri); oidc_util_get_request_parameter(r, OIDC_DISC_LH_PARAM, &login_hint); oidc_util_get_request_parameter(r, OIDC_DISC_SC_PARAM, &path_scopes); oidc_util_get_request_parameter(r, OIDC_DISC_AR_PARAM, &auth_request_params); oidc_util_get_request_parameter(r, OIDC_CSRF_NAME, &csrf_query); csrf_cookie = oidc_util_get_cookie(r, OIDC_CSRF_NAME); /* do CSRF protection if not 3rd party initiated SSO */ if (csrf_cookie) { /* clean CSRF cookie */ oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0, NULL); /* compare CSRF cookie value with query parameter value */ if ((csrf_query == NULL) || apr_strnatcmp(csrf_query, csrf_cookie) != 0) { oidc_warn(r, "CSRF protection failed, no Discovery and dynamic client registration will be allowed"); csrf_cookie = NULL; } } // TODO: trim issuer/accountname/domain input and do more input validation oidc_debug(r, "issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\", user=\"%s\"", issuer, target_link_uri, login_hint, user); if (target_link_uri == NULL) { if (c->default_sso_url == NULL) { return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "SSO to this module without specifying a \"target_link_uri\" parameter is not possible because " OIDCDefaultURL " is not set.", HTTP_INTERNAL_SERVER_ERROR); } target_link_uri = c->default_sso_url; } /* do open redirect prevention */ if (oidc_target_link_uri_matches_configuration(r, c, target_link_uri) == FALSE) { return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "\"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.", HTTP_UNAUTHORIZED); } /* see if this is a static setup */ if (c->metadata_dir == NULL) { if ((oidc_provider_static_config(r, c, &provider) == TRUE) && (issuer != NULL)) { if (apr_strnatcmp(provider->issuer, issuer) != 0) { return oidc_util_html_send_error(r, c->error_template, "Invalid Request", apr_psprintf(r->pool, "The \"iss\" value must match the configured providers' one (%s != %s).", issuer, c->provider.issuer), HTTP_INTERNAL_SERVER_ERROR); } } return oidc_authenticate_user(r, c, NULL, target_link_uri, login_hint, NULL, NULL, auth_request_params, path_scopes); } /* find out if the user entered an account name or selected an OP manually */ if (user != NULL) { if (login_hint == NULL) login_hint = apr_pstrdup(r->pool, user); /* normalize the user identifier */ if (strstr(user, "https://") != user) user = apr_psprintf(r->pool, "https://%s", user); /* got an user identifier as input, perform OP discovery with that */ if (oidc_proto_url_based_discovery(r, c, user, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "Could not resolve the provided user identifier to an OpenID Connect provider; check your syntax.", HTTP_NOT_FOUND); } /* issuer is set now, so let's continue as planned */ } else if (strstr(issuer, OIDC_STR_AT) != NULL) { if (login_hint == NULL) { login_hint = apr_pstrdup(r->pool, issuer); //char *p = strstr(issuer, OIDC_STR_AT); //*p = '\0'; } /* got an account name as input, perform OP discovery with that */ if (oidc_proto_account_based_discovery(r, c, issuer, &issuer) == FALSE) { /* something did not work out, show a user facing error */ return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "Could not resolve the provided account name to an OpenID Connect provider; check your syntax.", HTTP_NOT_FOUND); } /* issuer is set now, so let's continue as planned */ } /* strip trailing '/' */ int n = strlen(issuer); if (issuer[n - 1] == OIDC_CHAR_FORWARD_SLASH) issuer[n - 1] = '\0'; /* try and get metadata from the metadata directories for the selected OP */ if ((oidc_metadata_get(r, c, issuer, &provider, csrf_cookie != NULL) == TRUE) && (provider != NULL)) { /* now we've got a selected OP, send the user there to authenticate */ return oidc_authenticate_user(r, c, provider, target_link_uri, login_hint, NULL, NULL, auth_request_params, path_scopes); } /* something went wrong */ return oidc_util_html_send_error(r, c->error_template, "Invalid Request", "Could not find valid provider metadata for the selected OpenID Connect provider; contact the administrator", HTTP_NOT_FOUND); } static apr_uint32_t oidc_transparent_pixel[17] = { 0x474e5089, 0x0a1a0a0d, 0x0d000000, 0x52444849, 0x01000000, 0x01000000, 0x00000408, 0x0c1cb500, 0x00000002, 0x4144490b, 0x639c7854, 0x0000cffa, 0x02010702, 0x71311c9a, 0x00000000, 0x444e4549, 0x826042ae }; static apr_byte_t oidc_is_front_channel_logout(const char *logout_param_value) { return ((logout_param_value != NULL) && ((apr_strnatcmp(logout_param_value, OIDC_GET_STYLE_LOGOUT_PARAM_VALUE) == 0) || (apr_strnatcmp(logout_param_value, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0))); } /* * handle a local logout */ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *url) { oidc_debug(r, "enter (url=%s)", url); /* if there's no remote_user then there's no (stored) session to kill */ if (session->remote_user != NULL) { /* remove session state (cq. cache entry and cookie) */ oidc_session_kill(r, session); } /* see if this is the OP calling us */ if (oidc_is_front_channel_logout(url)) { /* set recommended cache control headers */ oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_P3P, "CAO PSA OUR"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_EXPIRES, "0"); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_X_FRAME_OPTIONS, "DENY"); /* see if this is PF-PA style logout in which case we return a transparent pixel */ const char *accept = oidc_util_hdr_in_accept_get(r); if ((apr_strnatcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0) || ((accept) && strstr(accept, OIDC_CONTENT_TYPE_IMAGE_PNG))) { return oidc_util_http_send(r, (const char *) &oidc_transparent_pixel, sizeof(oidc_transparent_pixel), OIDC_CONTENT_TYPE_IMAGE_PNG, DONE); } /* standard HTTP based logout: should be called in an iframe from the OP */ return oidc_util_html_send(r, "Logged Out", NULL, NULL, "

Logged Out

", DONE); } /* see if we don't need to go somewhere special after killing the session locally */ if (url == NULL) return oidc_util_html_send(r, "Logged Out", NULL, NULL, "

Logged Out

", DONE); /* send the user to the specified where-to-go-after-logout URL */ oidc_util_hdr_out_location_set(r, url); return HTTP_MOVED_TEMPORARILY; } /* * perform (single) logout */ static int oidc_handle_logout(request_rec *r, oidc_cfg *c, oidc_session_t *session) { /* pickup the command or URL where the user wants to go after logout */ char *url = NULL; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url); oidc_debug(r, "enter (url=%s)", url); if (oidc_is_front_channel_logout(url)) { return oidc_handle_logout_request(r, c, session, url); } if ((url == NULL) || (apr_strnatcmp(url, "") == 0)) { url = c->default_slo_url; } else { /* do input validation on the logout parameter value */ const char *error_description = NULL; apr_uri_t uri; if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) { const char *error_description = apr_psprintf(r->pool, "Logout URL malformed: %s", url); oidc_error(r, "%s", error_description); return oidc_util_html_send_error(r, c->error_template, "Malformed URL", error_description, HTTP_INTERNAL_SERVER_ERROR); } if ((strstr(r->hostname, uri.hostname) == NULL) || (strstr(uri.hostname, r->hostname) == NULL)) { error_description = apr_psprintf(r->pool, "logout value \"%s\" does not match the hostname of the current request \"%s\"", apr_uri_unparse(r->pool, &uri, 0), r->hostname); oidc_error(r, "%s", error_description); return oidc_util_html_send_error(r, c->error_template, "Invalid Request", error_description, HTTP_INTERNAL_SERVER_ERROR); } /* validate the URL to prevent HTTP header splitting */ if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) { error_description = apr_psprintf(r->pool, "logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)", url); oidc_error(r, "%s", error_description); return oidc_util_html_send_error(r, c->error_template, "Invalid Request", error_description, HTTP_INTERNAL_SERVER_ERROR); } } const char *end_session_endpoint = oidc_session_get_logout_endpoint(r, session); if (end_session_endpoint != NULL) { const char *id_token_hint = oidc_session_get_idtoken(r, session); char *logout_request = apr_pstrdup(r->pool, end_session_endpoint); if (id_token_hint != NULL) { logout_request = apr_psprintf(r->pool, "%s%sid_token_hint=%s", logout_request, strchr(logout_request ? logout_request : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_util_escape_string(r, id_token_hint)); } if (url != NULL) { logout_request = apr_psprintf(r->pool, "%s%spost_logout_redirect_uri=%s", logout_request, strchr(logout_request ? logout_request : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_util_escape_string(r, url)); } url = logout_request; } return oidc_handle_logout_request(r, c, session, url); } /* * handle request for JWKs */ int oidc_handle_jwks(request_rec *r, oidc_cfg *c) { /* pickup requested JWKs type */ // char *jwks_type = NULL; // oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS, &jwks_type); char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : ["); apr_hash_index_t *hi = NULL; apr_byte_t first = TRUE; oidc_jose_error_t err; if (c->public_keys != NULL) { /* loop over the RSA public keys */ for (hi = apr_hash_first(r->pool, c->public_keys); hi; hi = apr_hash_next(hi)) { const char *s_kid = NULL; oidc_jwk_t *jwk = NULL; char *s_json = NULL; apr_hash_this(hi, (const void**) &s_kid, NULL, (void**) &jwk); if (oidc_jwk_to_json(r->pool, jwk, &s_json, &err) == TRUE) { jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",", s_json); first = FALSE; } else { oidc_error(r, "could not convert RSA JWK to JSON using oidc_jwk_to_json: %s", oidc_jose_e2s(r->pool, err)); } } } // TODO: send stuff if first == FALSE? jwks = apr_psprintf(r->pool, "%s ] }", jwks); return oidc_util_http_send(r, jwks, strlen(jwks), OIDC_CONTENT_TYPE_JSON, DONE); } static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *check_session_iframe) { oidc_debug(r, "enter"); oidc_util_hdr_out_location_set(r, check_session_iframe); return HTTP_MOVED_TEMPORARILY; } static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *c, oidc_session_t *session, const char *client_id, const char *check_session_iframe) { oidc_debug(r, "enter"); const char *java_script = " \n"; /* determine the origin for the check_session_iframe endpoint */ char *origin = apr_pstrdup(r->pool, check_session_iframe); apr_uri_t uri; apr_uri_parse(r->pool, check_session_iframe, &uri); char *p = strstr(origin, uri.path); *p = '\0'; /* the element identifier for the OP iframe */ const char *op_iframe_id = "openidc-op"; /* restore the OP session_state from the session */ const char *session_state = oidc_session_get_session_state(r, session); if (session_state == NULL) { oidc_warn(r, "no session_state found in the session; the OP does probably not support session management!?"); return DONE; } char *s_poll_interval = NULL; oidc_util_get_request_parameter(r, "poll", &s_poll_interval); if (s_poll_interval == NULL) s_poll_interval = "3000"; const char *redirect_uri = oidc_get_redirect_uri(r, c); java_script = apr_psprintf(r->pool, java_script, origin, client_id, session_state, op_iframe_id, s_poll_interval, redirect_uri, redirect_uri); return oidc_util_html_send(r, NULL, java_script, "setTimer", NULL, DONE); } /* * handle session management request */ static int oidc_handle_session_management(request_rec *r, oidc_cfg *c, oidc_session_t *session) { char *cmd = NULL; const char *id_token_hint = NULL, *client_id = NULL, *check_session_iframe = NULL; oidc_provider_t *provider = NULL; /* get the command passed to the session management handler */ oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_SESSION, &cmd); if (cmd == NULL) { oidc_error(r, "session management handler called with no command"); return HTTP_INTERNAL_SERVER_ERROR; } /* see if this is a local logout during session management */ if (apr_strnatcmp("logout", cmd) == 0) { oidc_debug(r, "[session=logout] calling oidc_handle_logout_request because of session mgmt local logout call."); return oidc_handle_logout_request(r, c, session, c->default_slo_url); } /* see if this is a request for the OP iframe */ if (apr_strnatcmp("iframe_op", cmd) == 0) { check_session_iframe = oidc_session_get_check_session_iframe(r, session); if (check_session_iframe != NULL) { return oidc_handle_session_management_iframe_op(r, c, session, check_session_iframe); } return HTTP_NOT_FOUND; } /* see if this is a request for the RP iframe */ if (apr_strnatcmp("iframe_rp", cmd) == 0) { client_id = oidc_session_get_client_id(r, session); check_session_iframe = oidc_session_get_check_session_iframe(r, session); if ((client_id != NULL) && (check_session_iframe != NULL)) { return oidc_handle_session_management_iframe_rp(r, c, session, client_id, check_session_iframe); } oidc_debug(r, "iframe_rp command issued but no client (%s) and/or no check_session_iframe (%s) set", client_id, check_session_iframe); return HTTP_NOT_FOUND; } /* see if this is a request check the login state with the OP */ if (apr_strnatcmp("check", cmd) == 0) { id_token_hint = oidc_session_get_idtoken(r, session); oidc_get_provider_from_session(r, c, session, &provider); if ((session->remote_user != NULL) && (provider != NULL)) { /* * TODO: this doesn't work with per-path provided auth_request_params and scopes * as oidc_dir_cfg_path_auth_request_params and oidc_dir_cfg_path_scope will pick * those for the redirect_uri itself; do we need to store those as part of the * session now? */ return oidc_authenticate_user(r, c, provider, apr_psprintf(r->pool, "%s?session=iframe_rp", oidc_get_redirect_uri_iss(r, c, provider)), NULL, id_token_hint, "none", oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); } oidc_debug(r, "[session=check] calling oidc_handle_logout_request because no session found."); return oidc_session_redirect_parent_window_to_logout(r, c); } /* handle failure in fallthrough */ oidc_error(r, "unknown command: %s", cmd); return HTTP_INTERNAL_SERVER_ERROR; } /* * handle refresh token request */ static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c, oidc_session_t *session) { char *return_to = NULL; char *r_access_token = NULL; char *error_code = NULL; /* get the command passed to the session management handler */ oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH, &return_to); oidc_util_get_request_parameter(r, OIDC_PROTO_ACCESS_TOKEN, &r_access_token); /* check the input parameters */ if (return_to == NULL) { oidc_error(r, "refresh token request handler called with no URL to return to"); return HTTP_INTERNAL_SERVER_ERROR; } if (r_access_token == NULL) { oidc_error(r, "refresh token request handler called with no access_token parameter"); error_code = "no_access_token"; goto end; } const char *s_access_token = oidc_session_get_access_token(r, session); if (s_access_token == NULL) { oidc_error(r, "no existing access_token found in the session, nothing to refresh"); error_code = "no_access_token_exists"; goto end; } /* compare the access_token parameter used for XSRF protection */ if (apr_strnatcmp(s_access_token, r_access_token) != 0) { oidc_error(r, "access_token passed in refresh request does not match the one stored in the session"); error_code = "no_access_token_match"; goto end; } /* get a handle to the provider configuration */ oidc_provider_t *provider = NULL; if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) { error_code = "session_corruption"; goto end; } /* execute the actual refresh grant */ if (oidc_refresh_access_token(r, c, session, provider, NULL) == FALSE) { oidc_error(r, "access_token could not be refreshed"); error_code = "refresh_failed"; goto end; } /* pass the tokens to the application and save the session, possibly updating the expiry */ if (oidc_session_pass_tokens_and_save(r, c, session, TRUE) == FALSE) { error_code = "session_corruption"; goto end; } end: /* pass optional error message to the return URL */ if (error_code != NULL) return_to = apr_psprintf(r->pool, "%s%serror_code=%s", return_to, strchr(return_to ? return_to : "", OIDC_CHAR_QUERY) ? OIDC_STR_AMP : OIDC_STR_QUERY, oidc_util_escape_string(r, error_code)); /* add the redirect location header */ oidc_util_hdr_out_location_set(r, return_to); return HTTP_MOVED_TEMPORARILY; } /* * handle request object by reference request */ static int oidc_handle_request_uri(request_rec *r, oidc_cfg *c) { char *request_ref = NULL; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, &request_ref); if (request_ref == NULL) { oidc_error(r, "no \"%s\" parameter found", OIDC_REDIRECT_URI_REQUEST_REQUEST_URI); return HTTP_BAD_REQUEST; } char *jwt = NULL; oidc_cache_get_request_uri(r, request_ref, &jwt); if (jwt == NULL) { oidc_error(r, "no cached JWT found for %s reference: %s", OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, request_ref); return HTTP_NOT_FOUND; } oidc_cache_set_request_uri(r, request_ref, NULL, 0); return oidc_util_http_send(r, jwt, strlen(jwt), OIDC_CONTENT_TYPE_JWT, DONE); } /* * handle a request to invalidate a cached access token introspection result */ static int oidc_handle_remove_at_cache(request_rec *r, oidc_cfg *c) { char *access_token = NULL; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, &access_token); char *cache_entry = NULL; oidc_cache_get_access_token(r, access_token, &cache_entry); if (cache_entry == NULL) { oidc_error(r, "no cached access token found for value: %s", access_token); return HTTP_NOT_FOUND; } oidc_cache_set_access_token(r, access_token, NULL, 0); return DONE; } #define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval" /* * handle request for session info */ static int oidc_handle_info_request(request_rec *r, oidc_cfg *c, oidc_session_t *session) { apr_byte_t needs_save = FALSE; char *s_format = NULL, *s_interval = NULL; oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO, &s_format); oidc_util_get_request_parameter(r, OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval); /* see if this is a request for a format that is supported */ if (apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) != 0) { oidc_warn(r, "request for unknown format: %s", s_format); return HTTP_UNSUPPORTED_MEDIA_TYPE; } /* check that we actually have a user session and this is someone calling with a proper session cookie */ if (session->remote_user == NULL) { oidc_warn(r, "no user session found"); return HTTP_UNAUTHORIZED; } /* set the user in the main request for further (incl. sub-request and authz) processing */ r->user = (char *) session->remote_user; if (c->info_hook_data == NULL) { oidc_warn(r, "no data configured to return in " OIDCInfoHook); return HTTP_NOT_FOUND; } /* see if we can and need to refresh the access token */ if ((s_interval != NULL) && (oidc_session_get_refresh_token(r, session) != NULL)) { apr_time_t t_interval; if (sscanf(s_interval, "%" APR_TIME_T_FMT, &t_interval) == 1) { t_interval = apr_time_from_sec(t_interval); /* get the last refresh timestamp from the session info */ apr_time_t last_refresh = oidc_session_get_access_token_last_refresh(r, session); oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds", apr_time_sec(last_refresh + t_interval - apr_time_now())); /* see if we need to refresh again */ if (last_refresh + t_interval < apr_time_now()) { /* get the current provider info */ oidc_provider_t *provider = NULL; if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; /* execute the actual refresh grant */ if (oidc_refresh_access_token(r, c, session, provider, NULL) == FALSE) oidc_warn(r, "access_token could not be refreshed"); else needs_save = TRUE; } } } /* create the JSON object */ json_t *json = json_object(); /* add a timestamp of creation in there for the caller */ if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_TIMESTAMP, APR_HASH_KEY_STRING)) { json_object_set_new(json, OIDC_HOOK_INFO_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); } /* * refresh the claims from the userinfo endpoint * side-effect is that this may refresh the access token if not already done * note that OIDCUserInfoRefreshInterval should be set to control the refresh policy */ needs_save |= oidc_refresh_claims_from_userinfo_endpoint(r, c, session); /* include the access token in the session info */ if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN, APR_HASH_KEY_STRING)) { const char *access_token = oidc_session_get_access_token(r, session); if (access_token != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN, json_string(access_token)); } /* include the access token expiry timestamp in the session info */ if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, APR_HASH_KEY_STRING)) { const char *access_token_expires = oidc_session_get_access_token_expires(r, session); if (access_token_expires != NULL) json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, json_string(access_token_expires)); } /* include the id_token claims in the session info */ if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ID_TOKEN, APR_HASH_KEY_STRING)) { json_t *id_token = oidc_session_get_idtoken_claims_json(r, session); if (id_token) json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN, id_token); } if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_USER_INFO, APR_HASH_KEY_STRING)) { /* include the claims from the userinfo endpoint the session info */ json_t *claims = oidc_session_get_userinfo_claims_json(r, session); if (claims) json_object_set_new(json, OIDC_HOOK_INFO_USER_INFO, claims); } if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION, APR_HASH_KEY_STRING)) { json_t *j_session = json_object(); json_object_set(j_session, OIDC_HOOK_INFO_SESSION_STATE, session->state); json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_UUID, json_string(session->uuid)); json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_EXP, json_integer(apr_time_sec(session->expiry))); json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_REMOTE_USER, json_string(session->remote_user)); json_object_set_new(json, OIDC_HOOK_INFO_SESSION, j_session); } if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_REFRESH_TOKEN, APR_HASH_KEY_STRING)) { /* include the refresh token in the session info */ const char *refresh_token = oidc_session_get_refresh_token(r, session); if (refresh_token != NULL) json_object_set_new(json, OIDC_HOOK_INFO_REFRESH_TOKEN, json_string(refresh_token)); } /* JSON-encode the result */ char *r_value = oidc_util_encode_json_object(r, json, 0); /* free the allocated resources */ json_decref(json); /* pass the tokens to the application and save the session, possibly updating the expiry */ if (oidc_session_pass_tokens_and_save(r, c, session, needs_save) == FALSE) { oidc_warn(r, "error saving session"); return HTTP_INTERNAL_SERVER_ERROR; } /* return the stringified JSON result */ return oidc_util_http_send(r, r_value, strlen(r_value), OIDC_CONTENT_TYPE_JSON, DONE); } /* * handle all requests to the redirect_uri */ int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c, oidc_session_t *session) { if (oidc_proto_is_redirect_authorization_response(r, c)) { /* this is an authorization response from the OP using the Basic Client profile or a Hybrid flow*/ return oidc_handle_redirect_authorization_response(r, c, session); } else if (oidc_proto_is_post_authorization_response(r, c)) { /* this is an authorization response using the fragment(+POST) response_mode with the Implicit Client profile */ return oidc_handle_post_authorization_response(r, c, session); } else if (oidc_is_discovery_response(r, c)) { /* this is response from the OP discovery page */ return oidc_handle_discovery_response(r, c); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT)) { /* handle logout */ return oidc_handle_logout(r, c, session); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) { /* handle JWKs request */ return oidc_handle_jwks(r, c); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_SESSION)) { /* handle session management request */ return oidc_handle_session_management(r, c, session); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH)) { /* handle refresh token request */ return oidc_handle_refresh_token_request(r, c, session); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI)) { /* handle request object by reference request */ return oidc_handle_request_uri(r, c); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) { /* handle request to invalidate access token cache */ return oidc_handle_remove_at_cache(r, c); } else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO)) { if (session->remote_user == NULL) return HTTP_UNAUTHORIZED; /* set remote user, set headers/env-vars, update expiry, update userinfo + AT */ return oidc_handle_existing_session(r, c, session); } else if ((r->args == NULL) || (apr_strnatcmp(r->args, "") == 0)) { /* this is a "bare" request to the redirect URI, indicating implicit flow using the fragment response_mode */ return oidc_proto_javascript_implicit(r, c); } /* this is not an authorization response or logout request */ /* check for "error" response */ if (oidc_util_request_has_parameter(r, OIDC_PROTO_ERROR)) { // char *error = NULL, *descr = NULL; // oidc_util_get_request_parameter(r, "error", &error); // oidc_util_get_request_parameter(r, "error_description", &descr); // // /* send user facing error to browser */ // return oidc_util_html_send_error(r, error, descr, DONE); oidc_handle_redirect_authorization_response(r, c, session); } oidc_error(r, "The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR", r->args); /* something went wrong */ return oidc_util_html_send_error(r, c->error_template, "Invalid Request", apr_psprintf(r->pool, "The OpenID Connect callback URL received an invalid request"), HTTP_INTERNAL_SERVER_ERROR); } #define OIDC_AUTH_TYPE_OPENID_CONNECT "openid-connect" #define OIDC_AUTH_TYPE_OPENID_OAUTH20 "oauth20" #define OIDC_AUTH_TYPE_OPENID_BOTH "auth-openidc" /* * main routine: handle OpenID Connect authentication */ static int oidc_check_userid_openidc(request_rec *r, oidc_cfg *c) { if (oidc_get_redirect_uri(r, c) == NULL) { oidc_error(r, "configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT "\" but " OIDCRedirectURI " has not been set"); return HTTP_INTERNAL_SERVER_ERROR; } /* check if this is a sub-request or an initial request */ if (ap_is_initial_req(r)) { int rc = OK; /* load the session from the request state; this will be a new "empty" session if no state exists */ oidc_session_t *session = NULL; oidc_session_load(r, &session); /* see if the initial request is to the redirect URI; this handles potential logout too */ if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) { /* handle request to the redirect_uri */ rc = oidc_handle_redirect_uri_request(r, c, session); /* free resources allocated for the session */ oidc_session_free(r, session); return rc; /* initial request to non-redirect URI, check if we have an existing session */ } else if (session->remote_user != NULL) { /* this is initial request and we already have a session */ rc = oidc_handle_existing_session(r, c, session); /* free resources allocated for the session */ oidc_session_free(r, session); /* strip any cookies that we need to */ oidc_strip_cookies(r); return rc; } /* free resources allocated for the session */ oidc_session_free(r, session); /* * else: initial request, we have no session and it is not an authorization or * discovery response: just hit the default flow for unauthenticated users */ } else { /* not an initial request, try to recycle what we've already established in the main request */ if (r->main != NULL) r->user = r->main->user; else if (r->prev != NULL) r->user = r->prev->user; if (r->user != NULL) { /* this is a sub-request and we have a session (headers will have been scrubbed and set already) */ oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user); /* * apparently request state can get lost in sub-requests, so let's see * if we need to restore id_token and/or claims from the session cache */ const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN); if (s_id_token == NULL) { oidc_session_t *session = NULL; oidc_session_load(r, &session); oidc_copy_tokens_to_request_state(r, session, NULL, NULL); /* free resources allocated for the session */ oidc_session_free(r, session); } /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } /* * else: not initial request, but we could not find a session, so: * just hit the default flow for unauthenticated users */ } return oidc_handle_unauthenticated_user(r, c); } /* * main routine: handle "mixed" OIDC/OAuth authentication */ static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg *c) { /* get the bearer access token from the Authorization header */ const char *access_token = NULL; if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE) return oidc_oauth_check_userid(r, c); /* no bearer token found: then treat this as a regular OIDC browser request */ return oidc_check_userid_openidc(r, c); } /* * generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines */ int oidc_check_user_id(request_rec *r) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* log some stuff about the incoming HTTP request */ oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d", r->parsed_uri.path, r->args, ap_is_initial_req(r)); /* see if any authentication has been defined at all */ if (ap_auth_type(r) == NULL) return DECLINED; /* see if we've configured OpenID Connect user authentication for this request */ if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_CONNECT) == 0) return oidc_check_userid_openidc(r, c); /* see if we've configured OAuth 2.0 access control for this request */ if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) return oidc_oauth_check_userid(r, c); /* see if we've configured "mixed mode" for this request */ if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_BOTH) == 0) return oidc_check_mixed_userid_oauth(r, c); /* this is not for us but for some other handler */ return DECLINED; } /* * get the claims and id_token from request state */ static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims, json_t **id_token) { const char *s_claims = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_CLAIMS); if (s_claims != NULL) oidc_util_decode_json_object(r, s_claims, claims); const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN); if (s_id_token != NULL) oidc_util_decode_json_object(r, s_id_token, id_token); } #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 /* * find out which action we need to take when encountering an unauthorized request */ static authz_status oidc_handle_unauthorized_user24(request_rec *r) { oidc_debug(r, "enter"); oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) { oidc_oauth_return_www_authenticate(r, "insufficient_scope", "Different scope(s) or other claims required"); return AUTHZ_DENIED; } /* see if we've configured OIDCUnAutzAction for this path */ switch (oidc_dir_cfg_unautz_action(r)) { // TODO: document that AuthzSendForbiddenOnFailure is required to return 403 FORBIDDEN case OIDC_UNAUTZ_RETURN403: case OIDC_UNAUTZ_RETURN401: return AUTHZ_DENIED; break; case OIDC_UNAUTZ_AUTHENTICATE: /* * exception handling: if this looks like a XMLHttpRequest call we * won't redirect the user and thus avoid creating a state cookie * for a non-browser (= Javascript) call that will never return from the OP */ if (oidc_is_xml_http_request(r) == TRUE) return AUTHZ_DENIED; break; } oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); const char *location = oidc_util_hdr_out_location_get(r); if (location != NULL) { oidc_debug(r, "send HTML refresh with authorization redirect: %s", location); char *html_head = apr_psprintf(r->pool, "", location); oidc_util_html_send(r, "Stepup Authentication", html_head, NULL, NULL, HTTP_UNAUTHORIZED); } return AUTHZ_DENIED; } /* * generic Apache >=2.4 authorization hook for this module * handles both OpenID Connect or OAuth 2.0 in the same way, based on the claims stored in the session */ authz_status oidc_authz_checker(request_rec *r, const char *require_args, const void *parsed_require_args, oidc_authz_match_claim_fn_type match_claim_fn) { oidc_debug(r, "enter"); /* check for anonymous access and PASS mode */ if (r->user != NULL && strlen(r->user) == 0) { r->user = NULL; if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS) return AUTHZ_GRANTED; } /* get the set of claims from the request state (they've been set in the authentication part earlier */ json_t *claims = NULL, *id_token = NULL; oidc_authz_get_claims_and_idtoken(r, &claims, &id_token); /* merge id_token claims (e.g. "iss") in to claims json object */ if (claims) oidc_util_json_merge(r, id_token, claims); /* dispatch to the >=2.4 specific authz routine */ authz_status rc = oidc_authz_worker24(r, claims ? claims : id_token, require_args, match_claim_fn); /* cleanup */ if (claims) json_decref(claims); if (id_token) json_decref(id_token); if ((rc == AUTHZ_DENIED) && ap_auth_type(r)) rc = oidc_handle_unauthorized_user24(r); return rc; } authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claim); } #ifdef USE_LIBJQ authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args) { return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claims_expr); } #endif #else /* * find out which action we need to take when encountering an unauthorized request */ static int oidc_handle_unauthorized_user22(request_rec *r) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) { oidc_oauth_return_www_authenticate(r, "insufficient_scope", "Different scope(s) or other claims required"); return HTTP_UNAUTHORIZED; } /* see if we've configured OIDCUnAutzAction for this path */ switch (oidc_dir_cfg_unautz_action(r)) { case OIDC_UNAUTZ_RETURN403: return HTTP_FORBIDDEN; case OIDC_UNAUTZ_RETURN401: return HTTP_UNAUTHORIZED; case OIDC_UNAUTZ_AUTHENTICATE: /* * exception handling: if this looks like a XMLHttpRequest call we * won't redirect the user and thus avoid creating a state cookie * for a non-browser (= Javascript) call that will never return from the OP */ if (oidc_is_xml_http_request(r) == TRUE) return HTTP_UNAUTHORIZED; } return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL, NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r)); } /* * generic Apache <2.4 authorization hook for this module * handles both OpenID Connect and OAuth 2.0 in the same way, based on the claims stored in the request context */ int oidc_auth_checker(request_rec *r) { /* check for anonymous access and PASS mode */ if (r->user != NULL && strlen(r->user) == 0) { r->user = NULL; if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS) return OK; } /* get the set of claims from the request state (they've been set in the authentication part earlier */ json_t *claims = NULL, *id_token = NULL; oidc_authz_get_claims_and_idtoken(r, &claims, &id_token); /* get the Require statements */ const apr_array_header_t * const reqs_arr = ap_requires(r); /* see if we have any */ const require_line * const reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL; if (!reqs_arr) { oidc_debug(r, "no require statements found, so declining to perform authorization."); return DECLINED; } /* merge id_token claims (e.g. "iss") in to claims json object */ if (claims) oidc_util_json_merge(r, id_token, claims); /* dispatch to the <2.4 specific authz routine */ int rc = oidc_authz_worker22(r, claims ? claims : id_token, reqs, reqs_arr->nelts); /* cleanup */ if (claims) json_decref(claims); if (id_token) json_decref(id_token); if ((rc == HTTP_UNAUTHORIZED) && ap_auth_type(r)) rc = oidc_handle_unauthorized_user22(r); return rc; } #endif /* * handle content generating requests */ int oidc_content_handler(request_rec *r) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); int rc = DECLINED; if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) { if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO)) { oidc_session_t *session = NULL; oidc_session_load(r, &session); /* handle request for session info */ rc = oidc_handle_info_request(r, c, session); /* free resources allocated for the session */ oidc_session_free(r, session); } } return rc; } extern const command_rec oidc_config_cmds[]; module AP_MODULE_DECLARE_DATA auth_openidc_module = { STANDARD20_MODULE_STUFF, oidc_create_dir_config, oidc_merge_dir_config, oidc_create_server_config, oidc_merge_server_config, oidc_config_cmds, oidc_register_hooks }; mod_auth_openidc-2.3.3/src/cache/file.c0000644000076500000240000003400613200625410017567 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a file storage backend * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include #include "../mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * header structure that holds the metadata info for a cache file entry */ typedef struct { /* length of the cached data */ apr_size_t len; /* cache expiry timestamp */ apr_time_t expire; } oidc_cache_file_info_t; /* * prefix that distinguishes mod_auth_openidc cache files from other files in the same directory (/tmp) */ #define OIDC_CACHE_FILE_PREFIX "mod-auth-openidc-" /* post config routine */ int oidc_cache_file_post_config(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache_file_dir == NULL) { /* by default we'll use the OS specified /tmp dir for cache files */ apr_temp_dir_get((const char **) &cfg->cache_file_dir, s->process->pool); } return OK; } /* * return the cache file name for a specified key */ static const char *oidc_cache_file_name(request_rec *r, const char *section, const char *key) { return apr_psprintf(r->pool, "%s%s-%s", OIDC_CACHE_FILE_PREFIX, section, oidc_util_escape_string(r, key)); } /* * return the fully qualified path name to a cache file for a specified key */ static const char *oidc_cache_file_path(request_rec *r, const char *section, const char *key) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return apr_psprintf(r->pool, "%s/%s", cfg->cache_file_dir, oidc_cache_file_name(r, section, key)); } /* * read a specified number of bytes from a cache file in to a preallocated buffer */ static apr_status_t oidc_cache_file_read(request_rec *r, const char *path, apr_file_t *fd, void *buf, const apr_size_t len) { apr_status_t rc = APR_SUCCESS; apr_size_t bytes_read = 0; char s_err[128]; /* (blocking) read the requested number of bytes */ rc = apr_file_read_full(fd, buf, len, &bytes_read); /* test for system errors */ if (rc != APR_SUCCESS) { oidc_error(r, "could not read from: %s (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } /* ensure that we've got the requested number of bytes */ if (bytes_read != len) { oidc_error(r, "could not read enough bytes from: \"%s\", bytes_read (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_read, len); rc = APR_EGENERAL; } return rc; } /* * write a specified number of bytes from a buffer to a cache file */ static apr_status_t oidc_cache_file_write(request_rec *r, const char *path, apr_file_t *fd, void *buf, const apr_size_t len) { apr_status_t rc = APR_SUCCESS; apr_size_t bytes_written = 0; char s_err[128]; /* (blocking) write the number of bytes in the buffer */ rc = apr_file_write_full(fd, buf, len, &bytes_written); /* check for a system error */ if (rc != APR_SUCCESS) { oidc_error(r, "could not write to: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* check that all bytes from the header were written */ if (bytes_written != len) { oidc_error(r, "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_written, len); return APR_EGENERAL; } return rc; } /* * get a value for the specified key from the cache */ static apr_byte_t oidc_cache_file_get(request_rec *r, const char *section, const char *key, const char **value) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; /* get the fully qualified path to the cache file based on the key name */ const char *path = oidc_cache_file_path(r, section, key); /* open the cache file if it exists, otherwise we just have a "regular" cache miss */ if (apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, r->pool) != APR_SUCCESS) { oidc_debug(r, "cache miss for key \"%s\"", key); return TRUE; } /* the file exists, now lock it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); /* move the read pointer to the very start of the cache file */ apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* read a header with metadata */ oidc_cache_file_info_t info; if ((rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t))) != APR_SUCCESS) goto error_close; /* check if this cache entry has already expired */ if (apr_time_now() >= info.expire) { /* yep, expired: unlock and close before deleting the cache file */ apr_file_unlock(fd); apr_file_close(fd); /* log this event */ oidc_debug(r, "cache entry \"%s\" expired, removing file \"%s\"", key, path); /* and kill it */ if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) { oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } /* nothing strange happened really */ return TRUE; } /* allocate space for the actual value based on the data size info in the header (+1 for \0 termination) */ *value = apr_palloc(r->pool, info.len); /* (blocking) read the requested data in to the buffer */ rc = oidc_cache_file_read(r, path, fd, (void *) *value, info.len); /* barf on failure */ if (rc != APR_SUCCESS) { oidc_error(r, "could not read cache value from \"%s\"", path); goto error_close; } /* we're done, unlock and close the file */ apr_file_unlock(fd); apr_file_close(fd); return TRUE; error_close: apr_file_unlock(fd); apr_file_close(fd); oidc_error(r, "return error status %d (%s)", rc, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } // TODO: make these configurable? #define OIDC_CACHE_FILE_LAST_CLEANED "last-cleaned" /* * delete all expired entries from the cache directory */ static apr_status_t oidc_cache_file_clean(request_rec *r) { apr_status_t rc = APR_SUCCESS; apr_dir_t *dir = NULL; apr_file_t *fd = NULL; apr_status_t i; apr_finfo_t fi; oidc_cache_file_info_t info; char s_err[128]; oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* get the path to the metadata file that holds "last cleaned" metadata info */ const char *metadata_path = oidc_cache_file_path(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED); /* open the metadata file if it exists */ if ((rc = apr_stat(&fi, metadata_path, APR_FINFO_MTIME, r->pool)) == APR_SUCCESS) { /* really only clean once per so much time, check that we haven not recently run */ if (apr_time_now() < fi.mtime + apr_time_from_sec(cfg->cache_file_clean_interval)) { oidc_debug(r, "last cleanup call was less than %d seconds ago (next one as early as in %" APR_TIME_T_FMT " secs)", cfg->cache_file_clean_interval, apr_time_sec( fi.mtime + apr_time_from_sec(cfg->cache_file_clean_interval) - apr_time_now())); return APR_SUCCESS; } /* time to clean, reset the modification time of the metadata file to reflect the timestamp of this cleaning cycle */ apr_file_mtime_set(metadata_path, apr_time_now(), r->pool); oidc_debug(r, "start cleaning cycle"); } else { /* no metadata file exists yet, create one (and open it) */ if ((rc = apr_file_open(&fd, metadata_path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "error creating cache timestamp file '%s' (%s)", metadata_path, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* and cleanup... */ if ((rc = apr_file_close(fd)) != APR_SUCCESS) { oidc_error(r, "error closing cache timestamp file '%s' (%s)", metadata_path, apr_strerror(rc, s_err, sizeof(s_err))); } } /* time to clean, open the cache directory */ if ((rc = apr_dir_open(&dir, cfg->cache_file_dir, r->pool)) != APR_SUCCESS) { oidc_error(r, "error opening cache directory '%s' for cleaning (%s)", cfg->cache_file_dir, apr_strerror(rc, s_err, sizeof(s_err))); return rc; } /* loop trough the cache file entries */ do { /* read the next entry from the directory */ i = apr_dir_read(&fi, APR_FINFO_NAME, dir); if (i == APR_SUCCESS) { /* skip non-cache entries, cq. the ".", ".." and the metadata file */ if ((fi.name[0] == OIDC_CHAR_DOT) || (strstr(fi.name, OIDC_CACHE_FILE_PREFIX) != fi.name) || ((apr_strnatcmp(fi.name, oidc_cache_file_name(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED)) == 0))) continue; /* get the fully qualified path to the cache file and open it */ const char *path = apr_psprintf(r->pool, "%s/%s", cfg->cache_file_dir, fi.name); if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "unable to open cache entry \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); continue; } /* read the header with cache metadata info */ rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t)); apr_file_close(fd); if (rc == APR_SUCCESS) { /* check if this entry expired, if not just continue to the next entry */ if (apr_time_now() < info.expire) continue; /* the cache entry expired, we're going to remove it so log that event */ oidc_debug(r, "cache entry (%s) expired, removing file \"%s\")", fi.name, path); } else { /* file open returned an error, log that */ oidc_error(r, "cache entry (%s) corrupted (%s), removing file \"%s\"", fi.name, apr_strerror(rc, s_err, sizeof(s_err)), path); } /* delete the cache file */ if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) { /* hrm, this will most probably happen again on the next run... */ oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } } } while (i == APR_SUCCESS); apr_dir_close(dir); return APR_SUCCESS; } /* * write a value for the specified key to the cache */ static apr_byte_t oidc_cache_file_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; /* get the fully qualified path to the cache file based on the key name */ const char *path = oidc_cache_file_path(r, section, key); /* only on writes (not on reads) we clean the cache first (if not done recently) */ oidc_cache_file_clean(r); /* just remove cache file if value is NULL */ if (value == NULL) { if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) { oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); } return TRUE; } /* try to open the cache file for writing, creating it if it does not exist */ if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "cache file \"%s\" could not be opened (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* lock the file and move the write pointer to the start of it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* construct the metadata for this cache entry in the header info */ oidc_cache_file_info_t info; info.expire = expiry; info.len = strlen(value) + 1; /* write the header */ if ((rc = oidc_cache_file_write(r, path, fd, &info, sizeof(oidc_cache_file_info_t))) != APR_SUCCESS) return FALSE; /* next write the value */ rc = oidc_cache_file_write(r, path, fd, (void *) value, info.len); /* unlock and close the written file */ apr_file_unlock(fd); apr_file_close(fd); /* log our success/failure */ oidc_debug(r, "%s entry for key \"%s\" in file of %" APR_SIZE_T_FMT " bytes", (rc == APR_SUCCESS) ? "successfully stored" : "could not store", key, info.len); return (rc == APR_SUCCESS); } oidc_cache_t oidc_cache_file = { "file", 1, oidc_cache_file_post_config, NULL, oidc_cache_file_get, oidc_cache_file_set, NULL }; mod_auth_openidc-2.3.3/src/cache/memcache.c0000644000076500000240000002222213200625410020407 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a memcache backend * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include "apr_general.h" #include "apr_strings.h" #include "apr_hash.h" #include "apr_memcache.h" #include #include #include #include "../mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; typedef struct oidc_cache_cfg_memcache_t { /* cache_type = memcache: memcache ptr */ apr_memcache_t *cache_memcache; } oidc_cache_cfg_memcache_t; /* create the cache context */ static void *oidc_cache_memcache_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_memcache_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_memcache_t)); context->cache_memcache = NULL; return context; } /* * initialize the memcache struct to a number of memcache servers */ static int oidc_cache_memcache_post_config(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache_cfg != NULL) return APR_SUCCESS; oidc_cache_cfg_memcache_t *context = oidc_cache_memcache_cfg_create( s->process->pool); cfg->cache_cfg = context; apr_status_t rv = APR_SUCCESS; int nservers = 0; char* split; char* tok; apr_pool_t *p = s->process->pool; if (cfg->cache_memcache_servers == NULL) { oidc_serror(s, "cache type is set to \"memcache\", but no valid " OIDCMemCacheServers " setting was found"); return HTTP_INTERNAL_SERVER_ERROR; } /* loop over the provided memcache servers to find out the number of servers configured */ char *cache_config = apr_pstrdup(p, cfg->cache_memcache_servers); split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok); while (split) { nservers++; split = apr_strtok(NULL, OIDC_STR_SPACE, &tok); } /* allocated space for the number of servers */ rv = apr_memcache_create(p, nservers, 0, &context->cache_memcache); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to create memcache object of '%d' size", nservers); return HTTP_INTERNAL_SERVER_ERROR; } /* loop again over the provided servers */ cache_config = apr_pstrdup(p, cfg->cache_memcache_servers); split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok); while (split) { apr_memcache_server_t* st; char* host_str; char* scope_id; apr_port_t port; /* parse out host and port */ rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to parse cache server: '%s'", split); return HTTP_INTERNAL_SERVER_ERROR; } if (host_str == NULL) { oidc_serror(s, "failed to parse cache server, no hostname specified: '%s'", split); return HTTP_INTERNAL_SERVER_ERROR; } if (port == 0) port = 11211; /* create the memcache server struct */ // TODO: tune this rv = apr_memcache_server_create(p, host_str, port, 0, 1, 1, 60, &st); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to create cache server: %s:%d", host_str, port); return HTTP_INTERNAL_SERVER_ERROR; } /* add the memcache server struct to the list */ rv = apr_memcache_add_server(context->cache_memcache, st); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to add cache server: %s:%d", host_str, port); return HTTP_INTERNAL_SERVER_ERROR; } /* go to the next entry */ split = apr_strtok(NULL, OIDC_STR_SPACE, &tok); } return OK; } #define OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE 64 /* * printout readable error messages about memcache failures */ static void oidc_cache_memcache_log_status_error(request_rec *r, const char *s, apr_status_t rv) { char s_err[OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE]; apr_strerror(rv, s_err, OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE); oidc_error(r, "%s returned an error: [%s]; check your that your memcache server is available/accessible.", s, s_err); } /* * assemble single key name based on section/key input */ static char *oidc_cache_memcache_get_key(apr_pool_t *pool, const char *section, const char *key) { return apr_psprintf(pool, "%s:%s", section, key); } /* * check dead/alive status for all servers */ static apr_byte_t oidc_cache_memcache_status(request_rec *r, oidc_cache_cfg_memcache_t *context) { int rc = TRUE; int i; for (i = 0; rc && i < context->cache_memcache->ntotal; i++) rc = rc && (context->cache_memcache->live_servers[0]->status != APR_MC_SERVER_DEAD); return rc; } /* * get a name/value pair from memcache */ static apr_byte_t oidc_cache_memcache_get(request_rec *r, const char *section, const char *key, const char **value) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_memcache_t *context = (oidc_cache_cfg_memcache_t *) cfg->cache_cfg; apr_size_t len = 0; /* get it */ apr_status_t rv = apr_memcache_getp(context->cache_memcache, r->pool, oidc_cache_memcache_get_key(r->pool, section, key), (char **) value, &len, NULL); if (rv == APR_NOTFOUND) { /* * NB: workaround the fact that the apr_memcache returns APR_NOTFOUND if a server has been marked dead */ if (oidc_cache_memcache_status(r, context) == FALSE) { oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv); return FALSE; } oidc_debug(r, "apr_memcache_getp: key %s not found in cache", oidc_cache_memcache_get_key(r->pool, section, key)); return TRUE; } else if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv); return FALSE; } /* do sanity checking on the string value */ if ((*value) && (strlen(*value) != len)) { oidc_error(r, "apr_memcache_getp returned less bytes than expected: strlen(value) [%zu] != len [%" APR_SIZE_T_FMT "]", strlen(*value), len); return FALSE; } return TRUE; } /* * store a name/value pair in memcache */ static apr_byte_t oidc_cache_memcache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_memcache_t *context = (oidc_cache_cfg_memcache_t *) cfg->cache_cfg; apr_status_t rv = APR_SUCCESS; /* see if we should be clearing this entry */ if (value == NULL) { rv = apr_memcache_delete(context->cache_memcache, oidc_cache_memcache_get_key(r->pool, section, key), 0); if (rv == APR_NOTFOUND) { oidc_debug(r, "apr_memcache_delete: key %s not found in cache", oidc_cache_memcache_get_key(r->pool, section, key)); rv = APR_SUCCESS; } else if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_delete", rv); } } else { /* calculate the timeout from now */ apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now()); /* store it */ rv = apr_memcache_set(context->cache_memcache, oidc_cache_memcache_get_key(r->pool, section, key), (char *) value, strlen(value), timeout, 0); if (rv != APR_SUCCESS) { oidc_cache_memcache_log_status_error(r, "apr_memcache_set", rv); } } return (rv == APR_SUCCESS); } oidc_cache_t oidc_cache_memcache = { "memcache", 1, oidc_cache_memcache_post_config, NULL, oidc_cache_memcache_get, oidc_cache_memcache_set, NULL }; mod_auth_openidc-2.3.3/src/cache/shm.c0000644000076500000240000002462613200625410017446 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a shared memory backend, FIFO-style * based on mod_auth_mellon code * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include "apr_shm.h" #include "../mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; typedef struct oidc_cache_cfg_shm_t { apr_shm_t *shm; oidc_cache_mutex_t *mutex; } oidc_cache_cfg_shm_t; /* size of key in cached key/value pairs */ #define OIDC_CACHE_SHM_KEY_MAX 512 /* represents one (fixed size) cache entry, cq. name/value string pair */ typedef struct oidc_cache_shm_entry_t { /* name of the cache entry */ char section_key[OIDC_CACHE_SHM_KEY_MAX]; /* last (read) access timestamp */ apr_time_t access; /* expiry timestamp */ apr_time_t expires; /* value of the cache entry */ char value[]; } oidc_cache_shm_entry_t; /* create the cache context */ static void *oidc_cache_shm_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_shm_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_shm_t)); context->shm = NULL; context->mutex = oidc_cache_mutex_create(pool); return context; } #define OIDC_CACHE_SHM_ADD_OFFSET(t, size) t = (oidc_cache_shm_entry_t *)((uint8_t *)t + size) /* * initialized the shared memory block in the parent process */ int oidc_cache_shm_post_config(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache_cfg != NULL) return APR_SUCCESS; oidc_cache_cfg_shm_t *context = oidc_cache_shm_cfg_create(s->process->pool); cfg->cache_cfg = context; /* create the shared memory segment */ apr_status_t rv = apr_shm_create(&context->shm, cfg->cache_shm_entry_size_max * cfg->cache_shm_size_max, NULL, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_shm_create failed to create shared memory segment"); return HTTP_INTERNAL_SERVER_ERROR; } /* initialize the whole segment to '/0' */ int i; oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm); for (i = 0; i < cfg->cache_shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) { t->section_key[0] = '\0'; t->access = 0; } if (oidc_cache_mutex_post_config(s, context->mutex, "shm") == FALSE) return HTTP_INTERNAL_SERVER_ERROR; oidc_sdebug(s, "initialized shared memory with a cache size (# entries) of: %d, and a max (single) entry size of: %d", cfg->cache_shm_size_max, cfg->cache_shm_entry_size_max); return OK; } /* * initialize the shared memory segment in a child process */ int oidc_cache_shm_child_init(apr_pool_t *p, server_rec *s) { oidc_cfg *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg; /* initialize the lock for the child process */ return oidc_cache_mutex_child_init(p, s, context->mutex); } /* * assemble single key name based on section/key input */ static char *oidc_cache_shm_get_key(apr_pool_t *pool, const char *section, const char *key) { return apr_psprintf(pool, "%s:%s", section, key); } /* * get a value from the shared memory cache */ static apr_byte_t oidc_cache_shm_get(request_rec *r, const char *section, const char *key, const char **value) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg; int i; const char *section_key = oidc_cache_shm_get_key(r->pool, section, key); *value = NULL; /* grab the global lock */ if (oidc_cache_mutex_lock(r, context->mutex) == FALSE) return FALSE; /* get the pointer to the start of the shared memory block */ oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm); /* loop over the block, looking for the key */ for (i = 0; i < cfg->cache_shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) { const char *tablekey = t->section_key; if ((tablekey != NULL) && (apr_strnatcmp(tablekey, section_key) == 0)) { /* found a match, check if it has expired */ if (t->expires > apr_time_now()) { /* update access timestamp */ t->access = apr_time_now(); *value = t->value; } else { /* clear the expired entry */ t->section_key[0] = '\0'; t->access = 0; } /* we safely can break now since we would not have found an expired match twice */ break; } } /* release the global lock */ oidc_cache_mutex_unlock(r, context->mutex); return TRUE; } /* * store a value in the shared memory cache */ static apr_byte_t oidc_cache_shm_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg; oidc_cache_shm_entry_t *match, *free, *lru; oidc_cache_shm_entry_t *t; apr_time_t current_time; int i; apr_time_t age; const char *section_key = oidc_cache_shm_get_key(r->pool, section, key); /* check that the passed in key is valid */ if (strlen(section_key) > OIDC_CACHE_SHM_KEY_MAX) { oidc_error(r, "could not store value since key size is too large (%s)", section_key); return FALSE; } /* check that the passed in value is valid */ if ((value != NULL) && (strlen(value) > (cfg->cache_shm_entry_size_max - sizeof(oidc_cache_shm_entry_t)))) { oidc_error(r, "could not store value since value size is too large (%llu > %lu); consider increasing " OIDCCacheShmEntrySizeMax "", (unsigned long long )strlen(value), (unsigned long )(cfg->cache_shm_entry_size_max - sizeof(oidc_cache_shm_entry_t))); return FALSE; } /* grab the global lock */ if (oidc_cache_mutex_lock(r, context->mutex) == FALSE) return FALSE; /* get a pointer to the shared memory block */ t = apr_shm_baseaddr_get(context->shm); /* get the current time */ current_time = apr_time_now(); /* loop over the block, looking for the key */ match = NULL; free = NULL; lru = t; for (i = 0; i < cfg->cache_shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) { /* see if this slot is free */ if (t->section_key[0] == '\0') { if (free == NULL) free = t; continue; } /* see if a value already exists for this key */ if (apr_strnatcmp(t->section_key, section_key) == 0) { match = t; break; } /* see if this slot has expired */ if (t->expires <= current_time) { if (free == NULL) free = t; continue; } /* see if this slot was less recently used than the current pointer */ if (t->access < lru->access) { lru = t; } } /* if we have no free slots, issue a warning about the LRU entry */ if (match == NULL && free == NULL) { age = (current_time - lru->access) / 1000000; if (age < 3600) { oidc_warn(r, "dropping LRU entry with age = %" APR_TIME_T_FMT "s, which is less than one hour; consider increasing the shared memory caching space (which is %d now) with the (global) " OIDCCacheShmMax " setting.", age, cfg->cache_shm_size_max); } } /* pick the best slot: choose one with a matching key over a free slot, over a least-recently-used one */ t = match ? match : (free ? free : lru); /* see if we need to clear or set the value */ if (value != NULL) { /* fill out the entry with the provided data */ strcpy(t->section_key, section_key); strcpy(t->value, value); t->expires = expiry; t->access = current_time; } else { t->section_key[0] = '\0'; t->access = 0; } /* release the global lock */ oidc_cache_mutex_unlock(r, context->mutex); return TRUE; } static int oidc_cache_shm_destroy(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg; apr_status_t rv = APR_SUCCESS; if (context->shm) { apr_global_mutex_lock(context->mutex->mutex); if (*context->mutex->sema == 1) { rv = apr_shm_destroy(context->shm); oidc_sdebug(s, "apr_shm_destroy returned: %d", rv); } context->shm = NULL; apr_global_mutex_unlock(context->mutex->mutex); } oidc_cache_mutex_destroy(s, context->mutex); return rv; } oidc_cache_t oidc_cache_shm = { "shm", 0, oidc_cache_shm_post_config, oidc_cache_shm_child_init, oidc_cache_shm_get, oidc_cache_shm_set, oidc_cache_shm_destroy }; mod_auth_openidc-2.3.3/src/cache/common.c0000644000076500000240000004474713200625410020155 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * core cache functions: locking, crypto and utils * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #ifndef WIN32 #include #endif #include "apr_general.h" #include #include #include #ifdef AP_NEED_SET_MUTEX_PERMS #include "unixd.h" #endif #include #include #include #include #include "../mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* create the cache lock context */ oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool) { oidc_cache_mutex_t *ctx = apr_pcalloc(pool, sizeof(oidc_cache_mutex_t)); ctx->mutex = NULL; ctx->mutex_filename = NULL; ctx->shm = NULL; ctx->sema = NULL; return ctx; } #define OIDC_CACHE_ERROR_STR_MAX 255 /* * convert a apr status code to a string */ char *oidc_cache_status2str(apr_status_t statcode) { char buf[OIDC_CACHE_ERROR_STR_MAX]; return apr_strerror(statcode, buf, OIDC_CACHE_ERROR_STR_MAX); } apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m, const char *type) { apr_status_t rv = APR_SUCCESS; const char *dir; /* construct the mutex filename */ apr_temp_dir_get(&dir, s->process->pool); m->mutex_filename = apr_psprintf(s->process->pool, "%s/mod_auth_openidc_%s_mutex.%ld.%pp", dir, type, (long int) getpid(), s); /* create the mutex lock */ rv = apr_global_mutex_create(&m->mutex, (const char *) m->mutex_filename, APR_LOCK_DEFAULT, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_global_mutex_create failed to create mutex on file %s: %s (%d)", m->mutex_filename, oidc_cache_status2str(rv), rv); return FALSE; } /* need this on Linux */ #ifdef AP_NEED_SET_MUTEX_PERMS #if MODULE_MAGIC_NUMBER_MAJOR >= 20081201 rv = ap_unixd_set_global_mutex_perms(m->mutex); #else rv = unixd_set_global_mutex_perms(m->mutex); #endif if (rv != APR_SUCCESS) { oidc_serror(s, "unixd_set_global_mutex_perms failed; could not set permissions: %s (%d)", oidc_cache_status2str(rv), rv); return FALSE; } #endif rv = apr_shm_create(&m->shm, sizeof(int), NULL, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_shm_create failed to create shared memory segment"); return FALSE; } m->sema = apr_shm_baseaddr_get(m->shm); *m->sema = 1; return TRUE; } /* * initialize the cache lock in a child process */ apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s, oidc_cache_mutex_t *m) { /* initialize the lock for the child process */ apr_status_t rv = apr_global_mutex_child_init(&m->mutex, (const char *) m->mutex_filename, p); if (rv != APR_SUCCESS) { oidc_serror(s, "apr_global_mutex_child_init failed to reopen mutex on file %s: %s (%d)", m->mutex_filename, oidc_cache_status2str(rv), rv); } else { apr_global_mutex_lock(m->mutex); m->sema = apr_shm_baseaddr_get(m->shm); (*m->sema)++; apr_global_mutex_unlock(m->mutex); } //oidc_sdebug(s, "semaphore: %d (m=%pp,s=%pp)", *m->sema, m, s); return rv; } /* * global lock */ apr_byte_t oidc_cache_mutex_lock(request_rec *r, oidc_cache_mutex_t *m) { apr_status_t rv = apr_global_mutex_lock(m->mutex); if (rv != APR_SUCCESS) oidc_error(r, "apr_global_mutex_lock() failed: %s (%d)", oidc_cache_status2str(rv), rv); return TRUE; } /* * global unlock */ apr_byte_t oidc_cache_mutex_unlock(request_rec *r, oidc_cache_mutex_t *m) { apr_status_t rv = apr_global_mutex_unlock(m->mutex); if (rv != APR_SUCCESS) oidc_error(r, "apr_global_mutex_unlock() failed: %s (%d)", oidc_cache_status2str(rv), rv); return TRUE; } /* * destroy mutex */ apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m) { apr_status_t rv = APR_SUCCESS; if (m->mutex != NULL) { apr_global_mutex_lock(m->mutex); (*m->sema)--; //oidc_sdebug(s, "semaphore: %d (m=%pp,s=%pp)", *m->sema, m->mutex, s); apr_global_mutex_unlock(m->mutex); if ((m->shm != NULL) && (*m->sema == 0)) { rv = apr_global_mutex_destroy(m->mutex); oidc_sdebug(s, "apr_global_mutex_destroy returned :%d", rv); m->mutex = NULL; rv = apr_shm_destroy(m->shm); oidc_sdebug(s, "apr_shm_destroy for semaphore returned: %d", rv); m->shm = NULL; rv = APR_SUCCESS; } } return rv; } #define oidc_cache_crypto_openssl_error(r, fmt, ...) \ oidc_error(r, "%s: %s", apr_psprintf(r->pool, fmt, ##__VA_ARGS__), ERR_error_string(ERR_get_error(), NULL)) #define OIDC_CACHE_CIPHER EVP_aes_256_gcm() #define OIDC_CACHE_TAG_LEN 16 #if (OPENSSL_VERSION_NUMBER >= 0x10100005L) #define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_AEAD_GET_TAG #define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_AEAD_SET_TAG #define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_AEAD_SET_IVLEN #else #define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_GCM_GET_TAG #define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_GCM_SET_TAG #define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN #endif /* * AES GCM encrypt */ static int oidc_cache_crypto_encrypt_impl(request_rec *r, unsigned char *plaintext, int plaintext_len, const unsigned char *aad, int aad_len, unsigned char *key, const unsigned char *iv, int iv_len, unsigned char *ciphertext, const unsigned char *tag, int tag_len) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; /* create and initialize the context */ if (!(ctx = EVP_CIPHER_CTX_new())) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new"); return -1; } /* initialize the encryption cipher */ if (!EVP_EncryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) { oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex"); return -1; } /* set IV length */ if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); return -1; } /* initialize key and IV */ if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) { oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex"); return -1; } /* provide AAD data */ if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)) { oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d", aad_len); return -1; } /* provide the message to be encrypted and obtain the encrypted output */ if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { oidc_cache_crypto_openssl_error(r, "EVP_EncryptUpdate ciphertext"); return -1; } ciphertext_len = len; /* * finalize the encryption; normally ciphertext bytes may be written at * this stage, but this does not occur in GCM mode */ if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { oidc_cache_crypto_openssl_error(r, "EVP_EncryptFinal_ex"); return -1; } ciphertext_len += len; /* get the tag */ if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_GET_TAG, tag_len, (void *) tag)) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); return -1; } /* clean up */ EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } /* * AES GCM decrypt */ static int oidc_cache_crypto_decrypt_impl(request_rec *r, unsigned char *ciphertext, int ciphertext_len, const unsigned char *aad, int aad_len, const unsigned char *tag, int tag_len, unsigned char *key, const unsigned char *iv, int iv_len, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx; int len; int plaintext_len; int ret; /* create and initialize the context */ if (!(ctx = EVP_CIPHER_CTX_new())) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new"); return -1; } /* initialize the decryption cipher */ if (!EVP_DecryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) { oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex"); return -1; } /* set IV length */ if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); return -1; } /* initialize key and IV */ if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex"); return -1; } /* provide AAD data */ if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) { oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d", aad_len); return -1; } /* provide the message to be decrypted and obtain the plaintext output */ if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate ciphertext"); return -1; } plaintext_len = len; /* set expected tag value; works in OpenSSL 1.0.1d and later */ if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_TAG, tag_len, (void *) tag)) { oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl"); return -1; } /* * finalize the decryption; a positive return value indicates success, * anything else is a failure - the plaintext is not trustworthy */ ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); /* clean up */ EVP_CIPHER_CTX_free(ctx); if (ret > 0) { /* success */ plaintext_len += len; return plaintext_len; } else { /* verify failed */ oidc_cache_crypto_openssl_error(r, "EVP_DecryptFinal_ex"); return -1; } } /* * static AAD value for encryption/decryption */ static const unsigned char OIDC_CACHE_CRYPTO_GCM_AAD[] = { 0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, 0x78, 0xde }; /* * static IV value for encryption/decryption */ static const unsigned char OIDC_CACHE_CRYPTO_GCM_IV[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; /* * AES GCM encrypt using the static AAD and IV */ static int oidc_cache_crypto_encrypt(request_rec *r, const char *plaintext, unsigned char *key, char **result) { char *encoded = NULL, *p = NULL, *e_tag = NULL; unsigned char *ciphertext = NULL; int plaintext_len, ciphertext_len, encoded_len, e_tag_len; unsigned char tag[OIDC_CACHE_TAG_LEN]; /* allocate space for the ciphertext */ plaintext_len = strlen(plaintext) + 1; ciphertext = apr_pcalloc(r->pool, (plaintext_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER))); ciphertext_len = oidc_cache_crypto_encrypt_impl(r, (unsigned char *) plaintext, plaintext_len, OIDC_CACHE_CRYPTO_GCM_AAD, sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), key, OIDC_CACHE_CRYPTO_GCM_IV, sizeof(OIDC_CACHE_CRYPTO_GCM_IV), ciphertext, tag, sizeof(tag)); /* base64url encode the resulting ciphertext */ encoded_len = oidc_base64url_encode(r, &encoded, (const char *) ciphertext, ciphertext_len, 1); if (encoded_len > 0) { p = encoded; /* now allocated space for the concatenated base64url encoded ciphertext and tag */ encoded = apr_pcalloc(r->pool, encoded_len + 1 + OIDC_CACHE_TAG_LEN + 1); memcpy(encoded, p, encoded_len); p = encoded + encoded_len; *p = OIDC_CHAR_DOT; p++; /* base64url encode the tag and append it in the buffer */ e_tag_len = oidc_base64url_encode(r, &e_tag, (const char *) tag, OIDC_CACHE_TAG_LEN, 1); memcpy(p, e_tag, e_tag_len); encoded_len += e_tag_len + 1; /* make sure the result is \0 terminated */ encoded[encoded_len] = '\0'; *result = encoded; } return encoded_len; } /* * AES GCM decrypt using the static AAD and IV */ static int oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value, unsigned char *key, unsigned char **plaintext) { int len = -1; /* grab the base64url-encoded tag after the "." */ char *encoded_tag = strstr(cache_value, "."); if (encoded_tag == NULL) { oidc_error(r, "corrupted cache value: no tag separator found in encrypted value"); return FALSE; } /* make sure we don't modify the original string since it may be just a pointer into the cache (shm) */ cache_value = apr_pstrmemdup(r->pool, cache_value, strlen(cache_value) - strlen(encoded_tag)); encoded_tag++; /* base64url decode the ciphertext */ char *d_bytes = NULL; int d_len = oidc_base64url_decode(r->pool, &d_bytes, cache_value); /* base64url decode the tag */ char *t_bytes = NULL; int t_len = oidc_base64url_decode(r->pool, &t_bytes, encoded_tag); /* see if we're still good to go */ if ((d_len > 0) && (t_len > 0)) { /* allocated space for the plaintext */ *plaintext = apr_pcalloc(r->pool, (d_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER) - 1)); /* decrypt the ciphertext providing the tag value */ len = oidc_cache_crypto_decrypt_impl(r, (unsigned char *) d_bytes, d_len, OIDC_CACHE_CRYPTO_GCM_AAD, sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), (unsigned char *) t_bytes, t_len, key, OIDC_CACHE_CRYPTO_GCM_IV, sizeof(OIDC_CACHE_CRYPTO_GCM_IV), *plaintext); /* check the result and make sure it is \0 terminated */ if (len > -1) { (*plaintext)[len] = '\0'; } else { *plaintext = NULL; } } return len; } /* * hash the crypto passhphrase so it has enough key length for AES GCM 256 */ static unsigned char *oidc_cache_hash_passphrase(request_rec *r, const char *passphrase) { unsigned char *key = NULL; unsigned int key_len = 0; oidc_jose_error_t err; if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256, (const unsigned char *) passphrase, strlen(passphrase), &key, &key_len, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text); return NULL; } return key; } /* * hash a cache key and a crypto passphrase so the result is suitable as an randomized cache key */ static char *oidc_cache_get_hashed_key(request_rec *r, const char *passphrase, const char *key) { char *input = apr_psprintf(r->pool, "%s:%s", passphrase, key); char *output = NULL; if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, input, &output) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error"); return NULL; } return output; } /* * get a key/value string pair from the cache, possibly decrypting it */ apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key, char **value) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt(r); apr_byte_t rc = TRUE; char *msg = NULL; oidc_debug(r, "enter: %s (section=%s, decrypt=%d, type=%s)", key, section, encrypted, cfg->cache->name); /* see if encryption is turned on */ if (encrypted == 1) key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase, key); /* get the value from the cache */ const char *cache_value = NULL; if (cfg->cache->get(r, section, key, &cache_value) == FALSE) { rc = FALSE; goto out; } /* see if it is any good */ if (cache_value == NULL) goto out; /* see if encryption is turned on */ if (encrypted == 0) { *value = apr_pstrdup(r->pool, cache_value); goto out; } rc = (oidc_cache_crypto_decrypt(r, cache_value, oidc_cache_hash_passphrase(r, cfg->crypto_passphrase), (unsigned char **) value) > 0); out: /* log the result */ msg = apr_psprintf(r->pool, "from %s cache backend for %skey %s", cfg->cache->name, encrypted ? "encrypted " : "", key); if (rc == TRUE) if (*value != NULL) oidc_debug(r, "cache hit: return %d bytes %s", *value ? (int )strlen(*value) : 0, msg); else oidc_debug(r, "cache miss %s", msg); else oidc_warn(r, "error retrieving value %s", msg); return rc; } /* * store a key/value string pair in the cache, possibly in encrypted form */ apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); int encrypted = oidc_cfg_cache_encrypt(r); char *encoded = NULL; apr_byte_t rc = FALSE; char *msg = NULL; oidc_debug(r, "enter: %s (section=%s, len=%d, encrypt=%d, ttl(s)=%" APR_TIME_T_FMT ", type=%s)", key, section, value ? (int )strlen(value) : 0, encrypted, apr_time_sec(expiry - apr_time_now()), cfg->cache->name); /* see if we need to encrypt */ if (encrypted == 1) { key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase, key); if (key == NULL) goto out; if (value != NULL) { if (oidc_cache_crypto_encrypt(r, value, oidc_cache_hash_passphrase(r, cfg->crypto_passphrase), &encoded) <= 0) goto out; value = encoded; } } /* store the resulting value in the cache */ rc = cfg->cache->set(r, section, key, value, expiry); out: /* log the result */ msg = apr_psprintf(r->pool, "%d bytes in %s cache backend for %skey %s", value ? (int) strlen(value) : 0, cfg->cache->name, encrypted ? "encrypted " : "", key); if (rc == TRUE) oidc_debug(r, "successfully stored %s", msg); else oidc_warn(r, "could NOT store %s", msg); return rc; } mod_auth_openidc-2.3.3/src/oauth.c0000644000076500000240000005613013202535004016730 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include "mod_auth_openidc.h" #include "parse.h" /* * validate an access token against the validation endpoint of the Authorization server and gets a response back */ static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg *c, const char *token, char **response) { oidc_debug(r, "enter"); char *basic_auth = NULL; char *bearer_auth = NULL; /* assemble parameters to call the token endpoint for validation */ apr_table_t *params = apr_table_make(r->pool, 4); /* add any configured extra static parameters to the introspection endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, c->oauth.introspection_endpoint_params); /* add the access_token itself */ apr_table_addn(params, c->oauth.introspection_token_param_name, token); /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, c, c->oauth.introspection_endpoint_auth, c->oauth.client_id, c->oauth.client_secret, c->oauth.introspection_endpoint_url, params, &basic_auth, &bearer_auth) == FALSE) return FALSE; /* call the endpoint with the constructed parameter set and return the resulting response */ return apr_strnatcmp(c->oauth.introspection_endpoint_method, OIDC_INTROSPECTION_METHOD_GET) == 0 ? oidc_util_http_get(r, c->oauth.introspection_endpoint_url, params, basic_auth, bearer_auth, c->oauth.ssl_validate_server, response, c->http_timeout_long, c->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key)) : oidc_util_http_post_form(r, c->oauth.introspection_endpoint_url, params, basic_auth, bearer_auth, c->oauth.ssl_validate_server, response, c->http_timeout_long, c->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key)); } /* * get the authorization header that should contain a bearer token */ apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token) { /* get the directory specific setting on how the token can be passed in */ apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(r); const char *cookie_name = oidc_cfg_dir_accept_token_in_option(r, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME); oidc_debug(r, "accept_token_in=%d", accept_token_in); *access_token = NULL; if ((accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) || (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT)) { /* get the authorization header */ const char *auth_line = oidc_util_hdr_in_authorization_get(r); if (auth_line) { oidc_debug(r, "authorization header found"); /* look for the Bearer keyword */ if (apr_strnatcasecmp( ap_getword(r->pool, &auth_line, OIDC_CHAR_SPACE), OIDC_PROTO_BEARER) == 0) { /* skip any spaces after the Bearer keyword */ while (apr_isspace(*auth_line)) { auth_line++; } /* copy the result in to the access_token */ *access_token = apr_pstrdup(r->pool, auth_line); } else { oidc_warn(r, "client used unsupported authentication scheme: %s", r->uri); } } } if ((*access_token == NULL) && (r->method_number == M_POST) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST)) { apr_table_t *params = apr_table_make(r->pool, 8); if (oidc_util_read_post_params(r, params) == TRUE) { *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); } } if ((*access_token == NULL) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY)) { apr_table_t *params = apr_table_make(r->pool, 8); oidc_util_read_form_encoded_params(r, params, r->args); *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); } if ((*access_token == NULL) && (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE)) { const char *auth_line = oidc_util_get_cookie(r, cookie_name); if (auth_line != NULL) { /* copy the result in to the access_token */ *access_token = apr_pstrdup(r->pool, auth_line); } else { oidc_warn(r, "no cookie found with name: %s", cookie_name); } } if (*access_token == NULL) { oidc_debug(r, "no bearer token found in the allowed methods: %s", oidc_accept_oauth_token_in2str(r->pool, accept_token_in)); return FALSE; } /* log some stuff */ oidc_debug(r, "bearer token: %s", *access_token); return TRUE; } /* * copy over space separated scope value but do it in an array for authorization purposes */ /* static void oidc_oauth_spaced_string_to_array(request_rec *r, json_t *src, const char *src_key, json_t *dst, const char *dst_key) { apr_hash_t *ht = NULL; apr_hash_index_t *hi = NULL; json_t *arr = NULL; json_t *src_val = json_object_get(src, src_key); if (src_val != NULL) ht = oidc_util_spaced_string_to_hashtable(r->pool, json_string_value(src_val)); if (ht != NULL) { arr = json_array(); for (hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) { const char *k; const char *v; apr_hash_this(hi, (const void**) &k, NULL, (void**) &v); json_array_append_new(arr, json_string(v)); } json_object_set_new(dst, dst_key, arr); } } */ /* * parse (custom/configurable) token expiry claim in introspection result */ static apr_byte_t oidc_oauth_parse_and_cache_token_expiry(request_rec *r, oidc_cfg *c, json_t *introspection_response, const char *expiry_claim_name, int expiry_format_absolute, int expiry_claim_is_mandatory, apr_time_t *cache_until) { oidc_debug(r, "expiry_claim_name=%s, expiry_format_absolute=%d, expiry_claim_is_mandatory=%d", expiry_claim_name, expiry_format_absolute, expiry_claim_is_mandatory); json_t *expiry = json_object_get(introspection_response, expiry_claim_name); if (expiry == NULL) { if (expiry_claim_is_mandatory) { oidc_error(r, "introspection response JSON object did not contain an \"%s\" claim", expiry_claim_name); return FALSE; } return TRUE; } if (!json_is_integer(expiry)) { if (expiry_claim_is_mandatory) { oidc_error(r, "introspection response JSON object contains a \"%s\" claim but it is not a JSON integer", expiry_claim_name); return FALSE; } oidc_warn(r, "introspection response JSON object contains a \"%s\" claim that is not an (optional) JSON integer: the introspection result will NOT be cached", expiry_claim_name); return TRUE; } json_int_t value = json_integer_value(expiry); if (value <= 0) { oidc_warn(r, "introspection response JSON object integer number value <= 0 (%ld); introspection result will not be cached", (long )value); return TRUE; } *cache_until = apr_time_from_sec(value); if (expiry_format_absolute == FALSE) (*cache_until) += apr_time_now(); return TRUE; } #define OIDC_OAUTH_CACHE_KEY_RESPONSE "r" #define OIDC_OAUTH_CACHE_KEY_TIMESTAMP "t" static apr_byte_t oidc_oauth_cache_access_token(request_rec *r, oidc_cfg *c, apr_time_t cache_until, const char *access_token, json_t *json) { oidc_debug(r, "caching introspection result"); json_t *cache_entry = json_object(); json_object_set(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE, json); json_object_set_new(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); char *cache_value = oidc_util_encode_json_object(r, cache_entry, JSON_COMPACT); /* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */ oidc_cache_set_access_token(r, access_token, cache_value, cache_until); json_decref(cache_entry); return TRUE; } static apr_byte_t oidc_oauth_get_cached_access_token(request_rec *r, oidc_cfg *c, const char *access_token, json_t **json) { json_t *cache_entry = NULL; char *s_cache_entry = NULL; /* see if we've got the claims for this access_token cached already */ oidc_cache_get_access_token(r, access_token, &s_cache_entry); if (s_cache_entry == NULL) return FALSE; /* json decode the cache entry */ if (oidc_util_decode_json_object(r, s_cache_entry, &cache_entry) == FALSE) { *json = NULL; return FALSE; } /* compare the timestamp against the freshness requirement */ json_t *v = json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP); apr_time_t now = apr_time_sec(apr_time_now()); int token_introspection_interval = oidc_cfg_token_introspection_interval(r); if ((token_introspection_interval > 0) && (now > json_integer_value(v) + token_introspection_interval)) { /* printout info about the event */ char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, apr_time_from_sec(json_integer_value(v))); oidc_debug(r, "token that was validated/cached at: [%s], does not meet token freshness requirement: %d)", buf, token_introspection_interval); /* invalidate the cache entry */ *json = NULL; json_decref(cache_entry); return FALSE; } oidc_debug(r, "returning cached introspection result that meets freshness requirements: %s", s_cache_entry); /* we've got a cached introspection result that is still valid for this path's requirements */ *json = json_deep_copy( json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE)); json_decref(cache_entry); return TRUE; } /* * resolve and validate an access_token against the configured Authorization Server */ static apr_byte_t oidc_oauth_resolve_access_token(request_rec *r, oidc_cfg *c, const char *access_token, json_t **token, char **response) { json_t *result = NULL; /* see if we've got the claims for this access_token cached already */ oidc_oauth_get_cached_access_token(r, c, access_token, &result); if (result == NULL) { char *s_json = NULL; /* not cached, go out and validate the access_token against the Authorization server and get the JSON claims back */ if (oidc_oauth_validate_access_token(r, c, access_token, &s_json) == FALSE) { oidc_error(r, "could not get a validation response from the Authorization server"); return FALSE; } /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, s_json, &result) == FALSE) return FALSE; json_t *active = json_object_get(result, OIDC_PROTO_ACTIVE); apr_time_t cache_until; if (active != NULL) { if (json_is_boolean(active)) { if (!json_is_true(active)) { oidc_debug(r, "\"%s\" boolean object with value \"false\" found in response JSON object", OIDC_PROTO_ACTIVE); json_decref(result); return FALSE; } } else if (json_is_string(active)) { if (apr_strnatcasecmp(json_string_value(active), "true") != 0) { oidc_debug(r, "\"%s\" string object with value that is not equal to \"true\" found in response JSON object: %s", OIDC_PROTO_ACTIVE, json_string_value(active)); json_decref(result); return FALSE; } } else { oidc_debug(r, "no \"%s\" boolean or string object found in response JSON object", OIDC_PROTO_ACTIVE); json_decref(result); return FALSE; } if (oidc_oauth_parse_and_cache_token_expiry(r, c, result, OIDC_CLAIM_EXP, TRUE, FALSE, &cache_until) == FALSE) { json_decref(result); return FALSE; } /* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */ oidc_oauth_cache_access_token(r, c, cache_until, access_token, result); } else { if (oidc_oauth_parse_and_cache_token_expiry(r, c, result, c->oauth.introspection_token_expiry_claim_name, apr_strnatcmp( c->oauth.introspection_token_expiry_claim_format, OIDC_CLAIM_FORMAT_ABSOLUTE) == 0, c->oauth.introspection_token_expiry_claim_required, &cache_until) == FALSE) { json_decref(result); return FALSE; } /* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */ oidc_oauth_cache_access_token(r, c, cache_until, access_token, result); } } /* return the access_token JSON object */ json_t *tkn = json_object_get(result, OIDC_PROTO_ACCESS_TOKEN); if ((tkn != NULL) && (json_is_object(tkn))) { /* * assume PingFederate validation: copy over those claims from the access_token * that are relevant for authorization purposes */ json_object_set(tkn, OIDC_PROTO_CLIENT_ID, json_object_get(result, OIDC_PROTO_CLIENT_ID)); json_object_set(tkn, OIDC_PROTO_SCOPE, json_object_get(result, OIDC_PROTO_SCOPE)); //oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, tkn, "scopes"); /* return only the pimped access_token results */ *token = json_deep_copy(tkn); json_decref(result); } else { //oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, result, "scopes"); /* assume spec compliant introspection */ *token = result; } /* stringify the response */ *response = oidc_util_encode_json_object(r, *token, JSON_COMPACT); return TRUE; } /* * validate a JWT access token (locally) * * TODO: document that we're reusing the following settings from the OIDC config section: * - JWKs URI refresh interval * - encryption key material (OIDCPrivateKeyFiles) * * OIDCOAuthRemoteUserClaim client_id * # 32x 61 hex * OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */ static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r, oidc_cfg *c, const char *access_token, json_t **token, char **response) { oidc_debug(r, "enter: JWT access_token header=%s", oidc_proto_peek_jwt_header(r, access_token, NULL)); oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, c->provider.client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; oidc_jwt_t *jwt = NULL; if (oidc_jwt_parse(r->pool, access_token, &jwt, oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk), &err) == FALSE) { oidc_error(r, "could not parse JWT from access_token: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully parsed JWT with header: %s", jwt->header.value.str); /* * validate the access token JWT by validating the (optional) exp claim * don't enforce anything around iat since it doesn't make much sense for access tokens */ if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE, -1) == FALSE) { oidc_jwt_destroy(jwt); return FALSE; } oidc_debug(r, "verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s", c->oauth.verify_public_keys ? apr_hash_count(c->oauth.verify_public_keys) : 0, c->oauth.verify_shared_keys ? apr_hash_count(c->oauth.verify_shared_keys) : 0, c->oauth.verify_jwks_uri); oidc_jwks_uri_t jwks_uri = { c->oauth.verify_jwks_uri, c->provider.jwks_refresh_interval, c->oauth.ssl_validate_server }; if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri, oidc_util_merge_key_sets(r->pool, c->oauth.verify_public_keys, c->oauth.verify_shared_keys)) == FALSE) { oidc_error(r, "JWT access token signature could not be validated, aborting"); oidc_jwt_destroy(jwt); return FALSE; } oidc_debug(r, "successfully verified JWT access token: %s", jwt->payload.value.str); *token = jwt->payload.value.json; *response = jwt->payload.value.str; return TRUE; } /* * set the WWW-Authenticate response header according to https://tools.ietf.org/html/rfc6750#section-3 */ int oidc_oauth_return_www_authenticate(request_rec *r, const char *error, const char *error_description) { char *hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BEARER); if (ap_auth_name(r) != NULL) hdr = apr_psprintf(r->pool, "%s %s=\"%s\"", hdr, OIDC_PROTO_REALM, ap_auth_name(r)); if (error != NULL) hdr = apr_psprintf(r->pool, "%s%s %s=\"%s\"", hdr, (ap_auth_name(r) ? "," : ""), OIDC_PROTO_ERROR, error); if (error_description != NULL) hdr = apr_psprintf(r->pool, "%s, %s=\"%s\"", hdr, OIDC_PROTO_ERROR_DESCRIPTION, error_description); oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_WWW_AUTHENTICATE, hdr); return HTTP_UNAUTHORIZED; } /* * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables */ static apr_byte_t oidc_oauth_set_request_user(request_rec *r, oidc_cfg *c, json_t *token) { char *remote_user = NULL; if (oidc_get_remote_user(r, c->oauth.remote_user_claim.claim_name, c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.replace, token, &remote_user) == FALSE) { oidc_error(r, "" OIDCOAuthRemoteUserClaim " is set to \"%s\", but could not set the remote user based the available claims for the user", c->oauth.remote_user_claim.claim_name); return FALSE; } r->user = remote_user; oidc_debug(r, "set user to \"%s\" based on claim: \"%s\"%s", r->user, c->oauth.remote_user_claim.claim_name, c->oauth.remote_user_claim.reg_exp ? apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"", c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.replace) : ""); return TRUE; } /* * main routine: handle OAuth 2.0 authentication/authorization */ int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c) { /* check if this is a sub-request or an initial request */ if (!ap_is_initial_req(r)) { if (r->main != NULL) r->user = r->main->user; else if (r->prev != NULL) r->user = r->prev->user; if (r->user != NULL) { /* this is a sub-request and we have a session */ oidc_debug(r, "recycling user '%s' from initial request for sub-request", r->user); /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } /* check if this is a request for the public (encryption) keys */ } else if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) { if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) { return oidc_handle_jwks(r, c); } } /* we don't have a session yet */ /* get the bearer access token from the Authorization header */ const char *access_token = NULL; if (oidc_oauth_get_bearer_token(r, &access_token) == FALSE) { if (r->method_number == M_OPTIONS) { r->user = ""; return OK; } return oidc_oauth_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_REQUEST, "No bearer token found in the request"); } /* validate the obtained access token against the OAuth AS validation endpoint */ json_t *token = NULL; char *s_token = NULL; /* check if an introspection endpoint is set */ if (c->oauth.introspection_endpoint_url != NULL) { /* we'll validate the token remotely */ if (oidc_oauth_resolve_access_token(r, c, access_token, &token, &s_token) == FALSE) return oidc_oauth_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "Reference token could not be introspected"); } else { /* no introspection endpoint is set, assume the token is a JWT and validate it locally */ if (oidc_oauth_validate_jwt_access_token(r, c, access_token, &token, &s_token) == FALSE) return oidc_oauth_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "JWT token could not be validated"); } /* check that we've got something back */ if (token == NULL) { oidc_error(r, "could not resolve claims (token == NULL)"); return oidc_oauth_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "No claims could be parsed from the token"); } /* store the parsed token (cq. the claims from the response) in the request state so it can be accessed by the authz routines */ oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, (const char *) s_token); /* set the request user */ if (oidc_oauth_set_request_user(r, c, token) == FALSE) { json_decref(token); oidc_error(r, "remote user could not be set, aborting with HTTP_UNAUTHORIZED"); return oidc_oauth_return_www_authenticate(r, OIDC_PROTO_ERR_INVALID_TOKEN, "Could not set remote user"); } /* * we're going to pass the information that we have to the application, * but first we need to scrub the headers that we're going to use for security reasons */ oidc_scrub_headers(r); /* set the user authentication HTTP header if set and required */ char *authn_header = oidc_cfg_dir_authn_header(r); apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r); apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r); if ((r->user != NULL) && (authn_header != NULL)) oidc_util_hdr_in_set(r, authn_header, r->user); /* set the resolved claims in the HTTP headers for the target application */ oidc_util_set_app_infos(r, token, oidc_cfg_claim_prefix(r), c->claim_delimiter, pass_headers, pass_envvars); /* set the access_token in the app headers */ if (access_token != NULL) { oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token, OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars); } /* free JSON resources */ json_decref(token); /* strip any cookies that we need to */ oidc_strip_cookies(r); return OK; } mod_auth_openidc-2.3.3/src/proto.c0000644000076500000240000030517413203320522016756 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include "mod_auth_openidc.h" #include "parse.h" #include #include extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * generate a random string value value of a specified length */ static apr_byte_t oidc_proto_generate_random_string(request_rec *r, char **output, int len) { unsigned char *bytes = apr_pcalloc(r->pool, len); if (apr_generate_random_bytes(bytes, len) != APR_SUCCESS) { oidc_error(r, "apr_generate_random_bytes returned an error"); return FALSE; } if (oidc_base64url_encode(r, output, (const char *) bytes, len, TRUE) <= 0) { oidc_error(r, "oidc_base64url_encode returned an error"); return FALSE; } return TRUE; } #define OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST "copy_from_request" #define OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST "copy_and_remove_from_request" /* * indicates wether a request parameter from the authorization request needs to be * copied and/or deleted to/from the protected request object based on the settings specified * in the "copy_from_request"/"copy_and_remove_from_request" JSON array in the request object */ static apr_byte_t oidc_proto_param_needs_action(json_t *request_object_config, const char *parameter_name, const char *action) { json_t *copy_from_request = json_object_get(request_object_config, action); size_t index = 0; while (index < json_array_size(copy_from_request)) { json_t *value = json_array_get(copy_from_request, index); if ((json_is_string(value)) && (apr_strnatcmp(json_string_value(value), parameter_name) == 0)) { return TRUE; } index++; } return FALSE; } /* context structure for copying request parameters */ typedef struct oidc_proto_copy_req_ctx_t { request_rec *r; json_t *request_object_config; oidc_jwt_t *request_object; apr_table_t *params2; } oidc_proto_copy_req_ctx_t; /* * copy a parameter key/value from the authorizion request to the * request object if the configuration setting says to include it */ static int oidc_proto_copy_from_request(void* rec, const char* name, const char* value) { oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *) rec; oidc_debug(ctx->r, "processing name: %s, value: %s", name, value); if (oidc_proto_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST) || oidc_proto_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { json_t *result = NULL; json_error_t json_error; result = json_loads(value, JSON_DECODE_ANY, &json_error); if (result == NULL) /* assume string */ result = json_string(value); if (result) { json_object_set_new(ctx->request_object->payload.value.json, name, json_deep_copy(result)); json_decref(result); } if (oidc_proto_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { apr_table_set(ctx->params2, name, name); } } return 1; } /* * delete a parameter key/value from the authorizion request if the configuration setting says to remove it */ static int oidc_proto_delete_from_request(void* rec, const char* name, const char* value) { oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *) rec; oidc_debug(ctx->r, "deleting from query paramters: name: %s, value: %s", name, value); if (oidc_proto_param_needs_action(ctx->request_object_config, name, OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) { apr_table_unset(ctx->params2, name); } return 1; } /* * obtain the public key for a provider to encrypt the request object with */ apr_byte_t oidc_proto_get_encryption_jwk_by_type(request_rec *r, oidc_cfg *cfg, struct oidc_provider_t *provider, int key_type, oidc_jwk_t **jwk) { oidc_jwks_uri_t jwks_uri = { provider->jwks_uri, provider->jwks_refresh_interval, provider->ssl_validate_server }; oidc_jose_error_t err; json_t *j_jwks = NULL; apr_byte_t force_refresh = TRUE; oidc_jwk_t *key = NULL; char *jwk_json = NULL; /* TODO: forcefully refresh now; we may want to relax that */ oidc_metadata_jwks_get(r, cfg, &jwks_uri, &j_jwks, &force_refresh); if (j_jwks == NULL) { oidc_error(r, "could not retrieve JSON Web Keys"); return FALSE; } json_t *keys = json_object_get(j_jwks, "keys"); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"keys\" array element is not a JSON array"); return FALSE; } int i; /* walk the set of published keys to find the first that has a matching type */ for (i = 0; i < json_array_size(keys); i++) { json_t *elem = json_array_get(keys, i); const char *use = json_string_value( json_object_get(elem, OIDC_JWK_USE)); if ((use != NULL) && (strcmp(use, OIDC_JWK_ENC) != 0)) { oidc_debug(r, "skipping key because of non-matching \"%s\": \"%s\"", OIDC_JWK_USE, use); continue; } if (oidc_jwk_parse_json(r->pool, elem, &key, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed: %s", oidc_jose_e2s(r->pool, err)); continue; } if (key_type == key->kty) { oidc_jwk_to_json(r->pool, key, &jwk_json, &err); oidc_debug(r, "found matching encryption key type for key: %s", jwk_json); *jwk = key; break; } oidc_jwk_destroy(key); } /* no need anymore for the parsed json_t contents, release the it */ json_decref(j_jwks); return (*jwk != NULL); } /* * generate a request object */ char *oidc_proto_create_request_object(request_rec *r, struct oidc_provider_t *provider, json_t * request_object_config, apr_table_t *params) { oidc_debug(r, "enter"); oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); /* create the request object value */ oidc_jwt_t *request_object = oidc_jwt_new(r->pool, TRUE, TRUE); /* set basic values: iss and aud */ json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_ISS, json_string(provider->client_id)); json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_AUD, json_string(provider->issuer)); /* add static values to the request object as configured in the .conf file; may override iss/aud */ oidc_util_json_merge(r, json_object_get(request_object_config, "static"), request_object->payload.value.json); /* copy parameters from the authorization request as configured in the .conf file */ apr_table_t *delete_from_query_params = apr_table_make(r->pool, 0); oidc_proto_copy_req_ctx_t data = { r, request_object_config, request_object, delete_from_query_params }; apr_table_do(oidc_proto_copy_from_request, &data, params, NULL); /* delete parameters from the query parameters of the authorization request as configured in the .conf file */ data.params2 = params; apr_table_do(oidc_proto_delete_from_request, &data, delete_from_query_params, NULL); /* debug logging */ oidc_debug(r, "request object: %s", oidc_util_encode_json_object(r, request_object->payload.value.json, JSON_COMPACT)); char *serialized_request_object = NULL; oidc_jose_error_t err; /* get the crypto settings from the configuration */ json_t *crypto = json_object_get(request_object_config, "crypto"); oidc_json_object_get_string(r->pool, crypto, "sign_alg", &request_object->header.alg, "none"); /* see if we need to sign the request object */ if (strcmp(request_object->header.alg, "none") != 0) { oidc_jwk_t *jwk = NULL; int jwk_needs_destroy = 0; switch (oidc_jwt_alg2kty(request_object)) { case CJOSE_JWK_KTY_RSA: if (cfg->private_keys != NULL) { apr_ssize_t klen = 0; apr_hash_index_t *hi = apr_hash_first(r->pool, cfg->private_keys); apr_hash_this(hi, (const void **) &request_object->header.kid, &klen, (void **) &jwk); } else { oidc_error(r, "no private keys have been configured to use for private_key_jwt client authentication (" OIDCPrivateKeyFiles ")"); } break; case CJOSE_JWK_KTY_OCT: oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL, FALSE, &jwk); jwk_needs_destroy = 1; break; default: oidc_error(r, "unsupported signing algorithm, no key type for algorithm: %s", request_object->header.alg); break; } if (jwk == NULL) { oidc_jwt_destroy(request_object); json_decref(request_object_config); return FALSE; } if (oidc_jwt_sign(r->pool, request_object, jwk, &err) == FALSE) { oidc_error(r, "signing Request Object failed: %s", oidc_jose_e2s(r->pool, err)); if (jwk_needs_destroy) oidc_jwk_destroy(jwk); oidc_jwt_destroy(request_object); json_decref(request_object_config); return FALSE; } if (jwk_needs_destroy) oidc_jwk_destroy(jwk); } oidc_jwt_t *jwe = oidc_jwt_new(r->pool, TRUE, FALSE); if (jwe == NULL) { oidc_error(r, "creating JWE failed"); oidc_jwt_destroy(request_object); json_decref(request_object_config); return FALSE; } oidc_json_object_get_string(r->pool, crypto, "crypt_alg", &jwe->header.alg, NULL); oidc_json_object_get_string(r->pool, crypto, "crypt_enc", &jwe->header.enc, NULL); char *cser = oidc_jwt_serialize(r->pool, request_object, &err); /* see if we need to encrypt the request object */ if (jwe->header.alg != NULL) { oidc_jwk_t *jwk = NULL; switch (oidc_jwt_alg2kty(jwe)) { case CJOSE_JWK_KTY_RSA: oidc_proto_get_encryption_jwk_by_type(r, cfg, provider, CJOSE_JWK_KTY_RSA, &jwk); break; case CJOSE_JWK_KTY_OCT: oidc_util_create_symmetric_key(r, provider->client_secret, oidc_alg2keysize(jwe->header.alg), OIDC_JOSE_ALG_SHA256, FALSE, &jwk); break; default: oidc_error(r, "unsupported encryption algorithm, no key type for algorithm: %s", request_object->header.alg); break; } if (jwk == NULL) { oidc_jwt_destroy(jwe); oidc_jwt_destroy(request_object); json_decref(request_object_config); return FALSE; } if (jwe->header.enc == NULL) jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A128CBC_HS256); if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, &serialized_request_object, &err) == FALSE) { oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_destroy(jwk); oidc_jwt_destroy(jwe); oidc_jwt_destroy(request_object); json_decref(request_object_config); return FALSE; } oidc_jwk_destroy(jwk); } else { /* should be sign only or "none" */ serialized_request_object = cser; } oidc_jwt_destroy(request_object); oidc_jwt_destroy(jwe); json_decref(request_object_config); oidc_debug(r, "serialized request object JWT header = \"%s\"", oidc_proto_peek_jwt_header(r, serialized_request_object, NULL)); oidc_debug(r, "serialized request object = \"%s\"", serialized_request_object); return serialized_request_object; } /* * generate a request object and pass it by reference in the authorization request */ static char *oidc_proto_create_request_uri(request_rec *r, struct oidc_provider_t *provider, json_t * request_object_config, const char *redirect_uri, apr_table_t *params) { oidc_debug(r, "enter"); /* see if we need to override the resolver URL, mostly for test purposes */ char *resolver_url = NULL; if (json_object_get(request_object_config, "url") != NULL) resolver_url = apr_pstrdup(r->pool, json_string_value( json_object_get(request_object_config, "url"))); else resolver_url = apr_pstrdup(r->pool, redirect_uri); char *serialized_request_object = oidc_proto_create_request_object(r, provider, request_object_config, params); /* generate a temporary reference, store the request object in the cache and generate a Request URI that references it */ char *request_uri = NULL; if (serialized_request_object != NULL) { char *request_ref = NULL; if (oidc_proto_generate_random_string(r, &request_ref, 16) == TRUE) { oidc_cache_set_request_uri(r, request_ref, serialized_request_object, apr_time_now() + apr_time_from_sec(OIDC_REQUEST_URI_CACHE_DURATION)); request_uri = apr_psprintf(r->pool, "%s?%s=%s", resolver_url, OIDC_PROTO_REQUEST_URI, oidc_util_escape_string(r, request_ref)); } } return request_uri; } /* * Generic function to generate request/request_object parameter with value */ static void oidc_proto_add_request_param(request_rec *r, struct oidc_provider_t *provider, const char *redirect_uri, apr_table_t *params) { /* parse the request object configuration from a string in to a JSON structure */ json_t *request_object_config = NULL; if (oidc_util_decode_json_object(r, provider->request_object, &request_object_config) == FALSE) return; /* request_uri is used as default parameter for sending Request Object */ char* parameter = OIDC_PROTO_REQUEST_URI; /* get request_object_type parameter from config */ json_t *request_object_type = json_object_get(request_object_config, "request_object_type"); if (request_object_type != NULL) { const char* request_object_type_str = json_string_value( request_object_type); if (request_object_type_str == NULL) { oidc_error(r, "Value of request_object_type in request_object config is not a string"); return; } /* ensure parameter variable to have a valid value */ if (strcmp(request_object_type_str, OIDC_PROTO_REQUEST_OBJECT) == 0) { parameter = OIDC_PROTO_REQUEST_OBJECT; } else if (strcmp(request_object_type_str, OIDC_PROTO_REQUEST_URI) != 0) { oidc_error(r, "Bad request_object_type in config: %s", request_object_type_str); return; } } /* create request value */ char * value = NULL; if (strcmp(parameter, OIDC_PROTO_REQUEST_URI) == 0) { /* parameter is "request_uri" */ value = oidc_proto_create_request_uri(r, provider, request_object_config, redirect_uri, params); apr_table_set(params, OIDC_PROTO_REQUEST_URI, value); } else { /* parameter is "request" */ value = oidc_proto_create_request_object(r, provider, request_object_config, params); apr_table_set(params, OIDC_PROTO_REQUEST_OBJECT, value); } } /* context structure for encoding parameters */ typedef struct oidc_proto_form_post_ctx_t { request_rec *r; const char *html_body; } oidc_proto_form_post_ctx_t; /* * add a key/value pair post parameter */ static int oidc_proto_add_form_post_param(void* rec, const char* key, const char* value) { oidc_proto_form_post_ctx_t *ctx = (oidc_proto_form_post_ctx_t *) rec; oidc_debug(ctx->r, "processing: %s=%s", key, value); ctx->html_body = apr_psprintf(ctx->r->pool, "%s \n", ctx->html_body, oidc_util_html_escape(ctx->r->pool, key), oidc_util_html_escape(ctx->r->pool, value)); return 1; } /* * make the browser POST parameters through Javascript auto-submit */ static int oidc_proto_html_post(request_rec *r, const char *url, apr_table_t *params) { oidc_debug(r, "enter"); const char *html_body = apr_psprintf(r->pool, "

Submitting Authentication Request...

\n" "
\n" "

\n", url); oidc_proto_form_post_ctx_t data = { r, html_body }; apr_table_do(oidc_proto_add_form_post_param, &data, params, NULL); html_body = apr_psprintf(r->pool, "%s%s", data.html_body, "

\n" "
\n"); return oidc_util_html_send(r, "Submitting...", NULL, "document.forms[0].submit()", html_body, DONE); } /* * send an OpenID Connect authorization request to the specified provider */ int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, const char *id_token_hint, const char *code_challenge, const char *auth_request_params, const char *path_scope) { /* log some stuff */ oidc_debug(r, "enter, issuer=%s, redirect_uri=%s, state=%s, proto_state=%s, code_challenge=%s, auth_request_params=%s, path_scope=%s", provider->issuer, redirect_uri, state, oidc_proto_state_to_string(r, proto_state), code_challenge, auth_request_params, path_scope); int rv = DONE; char *authorization_request = NULL; /* assemble parameters to call the token endpoint for validation */ apr_table_t *params = apr_table_make(r->pool, 4); /* add the response type */ apr_table_setn(params, OIDC_PROTO_RESPONSE_TYPE, oidc_proto_state_get_response_type(proto_state)); /* concat the per-path scopes with the per-provider scopes */ const char *scope = provider->scope; if (path_scope != NULL) scope = ((scope != NULL) && (apr_strnatcmp(scope, "") != 0)) ? apr_pstrcat(r->pool, scope, OIDC_STR_SPACE, path_scope, NULL) : path_scope; if (scope != NULL) { if (!oidc_util_spaced_string_contains(r->pool, scope, OIDC_PROTO_SCOPE_OPENID)) { oidc_warn(r, "the configuration for the \"%s\" parameter does not include the \"%s\" scope, your provider may not return an \"id_token\": %s", OIDC_PROTO_SCOPE, OIDC_PROTO_SCOPE_OPENID, provider->scope); } apr_table_setn(params, OIDC_PROTO_SCOPE, scope); } /* add the client ID */ apr_table_setn(params, OIDC_PROTO_CLIENT_ID, provider->client_id); /* add the state */ apr_table_setn(params, OIDC_PROTO_STATE, state); /* add the redirect uri */ apr_table_setn(params, OIDC_PROTO_REDIRECT_URI, redirect_uri); /* add the nonce if set */ const char *nonce = oidc_proto_state_get_nonce(proto_state); if (nonce != NULL) apr_table_setn(params, OIDC_PROTO_NONCE, nonce); /* add PKCE code challenge if set */ if (code_challenge != NULL) { apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge); apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD, provider->pkce->method); } /* add the response_mode if explicitly set */ const char *response_mode = oidc_proto_state_get_response_mode(proto_state); if (response_mode != NULL) apr_table_setn(params, OIDC_PROTO_RESPONSE_MODE, response_mode); /* add the login_hint if provided */ if (login_hint != NULL) apr_table_setn(params, OIDC_PROTO_LOGIN_HINT, login_hint); /* add the id_token_hint if provided */ if (id_token_hint != NULL) apr_table_setn(params, OIDC_PROTO_ID_TOKEN_HINT, id_token_hint); /* add the prompt setting if provided (e.g. "none" for no-GUI checks) */ const char *prompt = oidc_proto_state_get_prompt(proto_state); if (prompt != NULL) apr_table_setn(params, OIDC_PROTO_PROMPT, prompt); /* add any statically configured custom authorization request parameters */ if (provider->auth_request_params != NULL) oidc_util_table_add_query_encoded_params(r->pool, params, provider->auth_request_params); /* add any dynamically configured custom authorization request parameters */ if (auth_request_params != NULL) oidc_util_table_add_query_encoded_params(r->pool, params, auth_request_params); /* add request parameter (request or request_uri) if set */ if (provider->request_object != NULL) oidc_proto_add_request_param(r, provider, redirect_uri, params); /* send the full authentication request via POST or GET */ if (provider->auth_request_method == OIDC_AUTH_REQUEST_METHOD_POST) { /* construct a HTML POST auto-submit page with the authorization request parameters */ rv = oidc_proto_html_post(r, provider->authorization_endpoint_url, params); } else { /* construct the full authorization request URL */ authorization_request = oidc_util_http_query_encoded_url(r, provider->authorization_endpoint_url, params); // TODO: should also enable this when using the POST binding for the auth request /* see if we need to preserve POST parameters through Javascript/HTML5 storage */ if (oidc_post_preserve_javascript(r, authorization_request, NULL, NULL) == FALSE) { /* add the redirect location header */ oidc_util_hdr_out_location_set(r, authorization_request); /* and tell Apache to return an HTTP Redirect (302) message */ rv = HTTP_MOVED_TEMPORARILY; } } /* add a referred token binding request for the provider if enabled */ if ((provider->token_binding_policy > OIDC_TOKEN_BINDING_POLICY_DISABLED) && (oidc_util_get_provided_token_binding_id(r) != NULL)) oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_INCLUDE_REFERRED_TOKEN_BINDING_ID, "true"); /* cleanup */ oidc_proto_state_destroy(proto_state); /* log our exit code */ oidc_debug(r, "return: %d", rv); return rv; } /* * indicate whether the incoming HTTP POST request is an OpenID Connect Authorization Response */ apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *cfg) { /* prereq: this is a call to the configured redirect_uri; see if it is a POST */ return (r->method_number == M_POST); } /* * indicate whether the incoming HTTP GET request is an OpenID Connect Authorization Response */ apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg) { /* prereq: this is a call to the configured redirect_uri; see if it is a GET with state and id_token or code parameters */ return ((r->method_number == M_GET) && oidc_util_request_has_parameter(r, OIDC_PROTO_STATE) && (oidc_util_request_has_parameter(r, OIDC_PROTO_ID_TOKEN) || oidc_util_request_has_parameter(r, OIDC_PROTO_CODE))); } /* * generate a random value (nonce) to correlate request/response through browser state */ apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len) { return oidc_proto_generate_random_string(r, nonce, len); } /* * PCKE "plain" proto state */ static apr_byte_t oidc_proto_pkce_state_plain(request_rec *r, char **state) { return oidc_proto_generate_random_string(r, state, OIDC_PROTO_CODE_VERIFIER_LENGTH); } /* * PCKE "plain" code_challenge */ static apr_byte_t oidc_proto_pkce_challenge_plain(request_rec *r, const char *state, char **code_challenge) { *code_challenge = apr_pstrdup(r->pool, state); return TRUE; } /* * PCKE "plain" code_verifier */ static apr_byte_t oidc_proto_pkce_verifier_plain(request_rec *r, const char *state, char **code_verifier) { *code_verifier = apr_pstrdup(r->pool, state); return TRUE; } /* * PCKE "s256" proto state */ static apr_byte_t oidc_proto_pkce_state_s256(request_rec *r, char **state) { return oidc_proto_generate_random_string(r, state, OIDC_PROTO_CODE_VERIFIER_LENGTH); } /* * PCKE "s256" code_challenge */ static apr_byte_t oidc_proto_pkce_challenge_s256(request_rec *r, const char *state, char **code_challenge) { if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256, state, code_challenge) == FALSE) { oidc_error(r, "oidc_util_hash_string_and_base64url_encode returned an error for the code verifier"); return FALSE; } return TRUE; } /* * PCKE "s256" code_verifier */ static apr_byte_t oidc_proto_pkce_verifier_s256(request_rec *r, const char *state, char **code_verifier) { *code_verifier = apr_pstrdup(r->pool, state); return TRUE; } /* * PCKE "referred_tb" proto state */ static apr_byte_t oidc_proto_pkce_state_referred_tb(request_rec *r, char **state) { *state = NULL; return TRUE; } /* * PCKE "referred_tb" code_challenge */ static apr_byte_t oidc_proto_pkce_challenge_referred_tb(request_rec *r, const char *state, char **code_challenge) { // state should be NULL *code_challenge = OIDC_PKCE_METHOD_REFERRED_TB; return TRUE; } /* * PCKE "referred_tb" code_verifier */ static apr_byte_t oidc_proto_pkce_verifier_referred_tb(request_rec *r, const char *state, char **code_verifier) { const char *tb_id = oidc_util_get_provided_token_binding_id(r); *code_verifier = tb_id ? apr_pstrdup(r->pool, tb_id) : NULL; return TRUE; } /* * PKCE plain */ oidc_proto_pkce_t oidc_pkce_plain = { OIDC_PKCE_METHOD_PLAIN, oidc_proto_pkce_state_plain, oidc_proto_pkce_verifier_plain, oidc_proto_pkce_challenge_plain }; /* * PKCE s256 */ oidc_proto_pkce_t oidc_pkce_s256 = { OIDC_PKCE_METHOD_S256, oidc_proto_pkce_state_s256, oidc_proto_pkce_verifier_s256, oidc_proto_pkce_challenge_s256 }; /* * PKCE referred_tb */ oidc_proto_pkce_t oidc_pkce_referred_tb = { OIDC_PKCE_METHOD_REFERRED_TB, oidc_proto_pkce_state_referred_tb, oidc_proto_pkce_verifier_referred_tb, oidc_proto_pkce_challenge_referred_tb }; #define OIDC_PROTO_STATE_ISSUER "i" #define OIDC_PROTO_STATE_ORIGINAL_URL "ou" #define OIDC_PROTO_STATE_ORIGINAL_METHOD "om" #define OIDC_PROTO_STATE_RESPONSE_MODE "rm" #define OIDC_PROTO_STATE_RESPONSE_TYPE "rt" #define OIDC_PROTO_STATE_NONCE "n" #define OIDC_PROTO_STATE_TIMESTAMP "t" #define OIDC_PROTO_STATE_PROMPT "pr" #define OIDC_PROTO_STATE_PKCE_STATE "ps" #define OIDC_PROTO_STATE_STATE "s" static const char *oidc_proto_state_get_string_value( oidc_proto_state_t *proto_state, const char *name) { json_t *v = json_object_get(proto_state, name); return v ? json_string_value(v) : NULL; } static void oidc_proto_state_set_string_value(oidc_proto_state_t *proto_state, const char *name, const char *value) { json_object_set_new(proto_state, name, json_string(value)); } oidc_proto_state_t *oidc_proto_state_new() { return json_object(); } void oidc_proto_state_destroy(oidc_proto_state_t *proto_state) { json_decref(proto_state); } oidc_proto_state_t * oidc_proto_state_from_cookie(request_rec *r, oidc_cfg *c, const char *cookieValue) { json_t *result = NULL; oidc_util_jwt_verify(r, c->crypto_passphrase, cookieValue, &result); return result; } char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state) { char *cookieValue = NULL; oidc_util_jwt_create(r, c->crypto_passphrase, proto_state, &cookieValue); return cookieValue; } char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state) { return oidc_util_encode_json_object(r, proto_state, JSON_COMPACT); } const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ISSUER); } const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_NONCE); } apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state) { json_t *v = json_object_get(proto_state, OIDC_PROTO_STATE_TIMESTAMP); return v ? apr_time_from_sec(json_integer_value(v)) : -1; } const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_PROMPT); } const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_TYPE); } const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_MODE); } const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_URL); } const char *oidc_proto_state_get_original_method( oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_METHOD); } const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_STATE); } const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state) { return oidc_proto_state_get_string_value(proto_state, OIDC_PROTO_STATE_PKCE_STATE); } void oidc_proto_state_set_state(oidc_proto_state_t *proto_state, const char *state) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_STATE, state); } void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state, const char *issuer) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ISSUER, issuer); } void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state, const char *original_url) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_URL, original_url); } void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state, const char *original_method) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ORIGINAL_METHOD, original_method); } void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state, const char *response_mode) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_MODE, response_mode); } void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state, const char *response_type) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_RESPONSE_TYPE, response_type); } void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state, const char *nonce) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_NONCE, nonce); } void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state, const char *prompt) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PROMPT, prompt); } void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state) { oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PKCE_STATE, pkce_state); } void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state) { json_object_set_new(proto_state, OIDC_PROTO_STATE_TIMESTAMP, json_integer(apr_time_sec(apr_time_now()))); } /* * if a nonce was passed in the authorization request (and stored in the browser state), * check that it matches the nonce value in the id_token payload */ // non-static for test.c apr_byte_t oidc_proto_validate_nonce(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt) { oidc_jose_error_t err; /* see if we have this nonce cached already */ char *replay = NULL; oidc_cache_get_nonce(r, nonce, &replay); if (replay != NULL) { oidc_error(r, "the nonce value (%s) passed in the browser state was found in the cache already; possible replay attack!?", nonce); return FALSE; } /* get the "nonce" value in the id_token payload */ char *j_nonce = NULL; if (oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_NONCE, TRUE, &j_nonce, &err) == FALSE) { oidc_error(r, "id_token JSON payload did not contain a \"%s\" string: %s", OIDC_CLAIM_NONCE, oidc_jose_e2s(r->pool, err)); return FALSE; } /* see if the nonce in the id_token matches the one that we sent in the authorization request */ if (apr_strnatcmp(nonce, j_nonce) != 0) { oidc_error(r, "the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)", j_nonce, nonce); return FALSE; } /* * nonce cache duration (replay prevention window) is the 2x the configured * slack on the timestamp (+-) for token issuance plus 10 seconds for safety */ apr_time_t nonce_cache_duration = apr_time_from_sec( provider->idtoken_iat_slack * 2 + 10); /* store it in the cache for the calculated duration */ oidc_cache_set_nonce(r, nonce, nonce, apr_time_now() + nonce_cache_duration); oidc_debug(r, "nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds", nonce, apr_time_sec(nonce_cache_duration)); return TRUE; } /* * validate the "aud" and "azp" claims in the id_token payload */ static apr_byte_t oidc_proto_validate_aud_and_azp(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) { char *azp = NULL; oidc_jose_get_string(r->pool, id_token_payload->value.json, OIDC_CLAIM_AZP, FALSE, &azp, NULL); /* * the "azp" claim is only needed when the id_token has a single audience value and that audience * is different than the authorized party; it MAY be included even when the authorized party is * the same as the sole audience. */ if ((azp != NULL) && (apr_strnatcmp(azp, provider->client_id) != 0)) { oidc_error(r, "the \"%s\" claim (%s) is present in the id_token, but is not equal to the configured client_id (%s)", OIDC_CLAIM_AZP, azp, provider->client_id); return FALSE; } /* get the "aud" value from the JSON payload */ json_t *aud = json_object_get(id_token_payload->value.json, OIDC_CLAIM_AUD); if (aud != NULL) { /* check if it is a single-value */ if (json_is_string(aud)) { /* a single-valued audience must be equal to our client_id */ if (apr_strnatcmp(json_string_value(aud), provider->client_id) != 0) { oidc_error(r, "the configured client_id (%s) did not match the \"%s\" claim value (%s) in the id_token", provider->client_id, OIDC_CLAIM_AUD, json_string_value(aud)); return FALSE; } /* check if this is a multi-valued audience */ } else if (json_is_array(aud)) { if ((json_array_size(aud) > 1) && (azp == NULL)) { oidc_debug(r, "the \"%s\" claim value in the id_token is an array with more than 1 element, but \"%s\" claim is not present (a SHOULD in the spec...)", OIDC_CLAIM_AUD, OIDC_CLAIM_AZP); } if (oidc_util_json_array_has_value(r, aud, provider->client_id) == FALSE) { oidc_error(r, "our configured client_id (%s) could not be found in the array of values for \"%s\" claim", provider->client_id, OIDC_CLAIM_AUD); return FALSE; } } else { oidc_error(r, "id_token JSON payload \"%s\" claim is not a string nor an array", OIDC_CLAIM_AUD); return FALSE; } } else { oidc_error(r, "id_token JSON payload did not contain an \"%s\" claim", OIDC_CLAIM_AUD); return FALSE; } return TRUE; } #define OIDC_CLAIM_CNF "cnf" #define OIDC_CLAIM_CNF_TBH "tbh" /* * validate the "cnf" claims in the id_token payload */ static apr_byte_t oidc_proto_validate_cnf(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) { char *tbh_str = NULL; char *tbh = NULL; int tbh_len = -1; const char *tbp_str = NULL; char *tbp = NULL; int tbp_len = -1; unsigned char *tbp_hash = NULL; unsigned int tbp_hash_len = -1; oidc_debug(r, "enter: policy=%s", oidc_token_binding_policy2str(r->pool, provider->token_binding_policy)); if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_DISABLED) return TRUE; tbp_str = oidc_util_get_provided_token_binding_id(r); if (tbp_str == NULL) { oidc_debug(r, "no Provided Token Binding ID environment variable found"); goto out_err; } tbp_len = oidc_base64url_decode(r->pool, &tbp, tbp_str); if (tbp_len <= 0) { oidc_warn(r, "Provided Token Binding ID environment variable could not be decoded"); return FALSE; } if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256, (const unsigned char *) tbp, tbp_len, &tbp_hash, &tbp_hash_len, NULL) == FALSE) { oidc_warn(r, "hashing Provided Token Binding ID environment variable failed"); return FALSE; } json_t *cnf = json_object_get(id_token_payload->value.json, OIDC_CLAIM_CNF); if (cnf == NULL) { oidc_debug(r, "no \"cnf\" claim found in id_token"); goto out_err; } oidc_jose_get_string(r->pool, cnf, OIDC_CLAIM_CNF_TBH, FALSE, &tbh_str, NULL); if (tbh_str == NULL) { oidc_debug(r, " \"cnf\" claim found in id_token but no \"tbh\" claim inside found"); goto out_err; } tbh_len = oidc_base64url_decode(r->pool, &tbh, tbh_str); if (tbh_len <= 0) { oidc_warn(r, "cnf[\"tbh\"] provided but it could not be decoded"); return FALSE; } if (tbp_hash_len != tbh_len) { oidc_warn(r, "hash length of provided token binding ID environment variable: %d does not match length of cnf[\"tbh\"]: %d", tbp_hash_len, tbh_len); return FALSE; } if (memcmp(tbp_hash, tbh, tbh_len) != 0) { oidc_warn(r, "hash of provided token binding ID environment variable does not match cnf[\"tbh\"]"); return FALSE; } oidc_debug(r, "hash of provided token binding ID environment variable matches cnf[\"tbh\"]"); return TRUE; out_err: if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_OPTIONAL) return TRUE; if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_ENFORCED) return FALSE; // provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_REQURIED return (tbp_str == NULL); } /* * validate "iat" claim in JWT */ static apr_byte_t oidc_proto_validate_iat(request_rec *r, oidc_jwt_t *jwt, apr_byte_t is_mandatory, int slack) { /* get the current time */ apr_time_t now = apr_time_sec(apr_time_now()); /* sanity check for iat being set */ if (jwt->payload.iat == OIDC_JWT_CLAIM_TIME_EMPTY) { if (is_mandatory) { oidc_error(r, "JWT did not contain an \"%s\" number value", OIDC_CLAIM_IAT); return FALSE; } return TRUE; } /* see if we are asked to enforce a time window at all */ if (slack < 0) { oidc_debug(r, "slack for JWT set < 0, do not enforce boundary check"); return TRUE; } /* check if this id_token has been issued just now +- slack (default 10 minutes) */ if ((now - slack) > jwt->payload.iat) { oidc_error(r, "\"iat\" validation failure (%ld): JWT was issued more than %d seconds ago", (long )jwt->payload.iat, slack); return FALSE; } if ((now + slack) < jwt->payload.iat) { oidc_error(r, "\"iat\" validation failure (%ld): JWT was issued more than %d seconds in the future", (long )jwt->payload.iat, slack); return FALSE; } return TRUE; } /* * validate "exp" claim in JWT */ static apr_byte_t oidc_proto_validate_exp(request_rec *r, oidc_jwt_t *jwt, apr_byte_t is_mandatory) { /* get the current time */ apr_time_t now = apr_time_sec(apr_time_now()); /* sanity check for exp being set */ if (jwt->payload.exp == OIDC_JWT_CLAIM_TIME_EMPTY) { if (is_mandatory) { oidc_error(r, "JWT did not contain an \"%s\" number value", OIDC_CLAIM_EXP); return FALSE; } return TRUE; } /* see if now is beyond the JWT expiry timestamp */ apr_time_t expires = jwt->payload.exp; if (now > expires) { oidc_error(r, "\"exp\" validation failure (%ld): JWT expired %ld seconds ago", (long )expires, (long )(now - expires)); return FALSE; } return TRUE; } /* * validate a JSON Web token */ apr_byte_t oidc_proto_validate_jwt(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, apr_byte_t iat_is_mandatory, int iat_slack) { if (iss != NULL) { /* issuer is set and must match */ if (jwt->payload.iss == NULL) { oidc_error(r, "JWT did not contain an \"%s\" string (requested value: %s)", OIDC_CLAIM_ISS, iss); return FALSE; } /* check if the issuer matches the requested value */ if (oidc_util_issuer_match(iss, jwt->payload.iss) == FALSE) { oidc_error(r, "requested issuer (%s) does not match received \"%s\" value in id_token (%s)", iss, OIDC_CLAIM_ISS, jwt->payload.iss); return FALSE; } } /* check exp */ if (oidc_proto_validate_exp(r, jwt, exp_is_mandatory) == FALSE) return FALSE; /* check iat */ if (oidc_proto_validate_iat(r, jwt, iat_is_mandatory, iat_slack) == FALSE) return FALSE; return TRUE; } /* * check whether the provided JWT is a valid id_token for the specified "provider" */ static apr_byte_t oidc_proto_validate_idtoken(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *nonce) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_debug(r, "enter, jwt.header=\"%s\", jwt.payload=\"%s\", nonce=\"%s\"", jwt->header.value.str, jwt->payload.value.str, nonce); /* if a nonce is not passed, we're doing a ("code") flow where the nonce is optional */ if (nonce != NULL) { /* if present, verify the nonce */ if (oidc_proto_validate_nonce(r, cfg, provider, nonce, jwt) == FALSE) return FALSE; } /* validate the ID Token JWT, requiring iss match, and valid exp + iat */ if (oidc_proto_validate_jwt(r, jwt, provider->issuer, TRUE, TRUE, provider->idtoken_iat_slack) == FALSE) return FALSE; /* check if the required-by-spec "sub" claim is present */ if (jwt->payload.sub == NULL) { oidc_error(r, "id_token JSON payload did not contain the required-by-spec \"%s\" string value", OIDC_CLAIM_SUB); return FALSE; } /* verify the "aud" and "azp" values */ if (oidc_proto_validate_aud_and_azp(r, cfg, provider, &jwt->payload) == FALSE) return FALSE; /* verify the included token binding ID if provided */ if (oidc_proto_validate_cnf(r, cfg, provider, &jwt->payload) == FALSE) return FALSE; return TRUE; } /* * get the key from the JWKs that corresponds with the key specified in the header */ static apr_byte_t oidc_proto_get_key_from_jwks(request_rec *r, oidc_jwt_t *jwt, json_t *j_jwks, apr_hash_t *result) { apr_byte_t rc = TRUE; oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *jwk_json = NULL; /* get the (optional) thumbprint for comparison */ const char *x5t = oidc_jwt_hdr_get(jwt, OIDC_JWK_X5T); oidc_debug(r, "search for kid \"%s\" or thumbprint x5t \"%s\"", jwt->header.kid, x5t); /* get the "keys" JSON array from the JWKs object */ json_t *keys = json_object_get(j_jwks, OIDC_JWK_KEYS); if ((keys == NULL) || !(json_is_array(keys))) { oidc_error(r, "\"%s\" array element is not a JSON array", OIDC_JWK_KEYS); return FALSE; } int i; for (i = 0; i < json_array_size(keys); i++) { /* get the next element in the array */ json_t *elem = json_array_get(keys, i); if (oidc_jwk_parse_json(r->pool, elem, &jwk, &err) == FALSE) { oidc_warn(r, "oidc_jwk_parse_json failed: %s", oidc_jose_e2s(r->pool, err)); continue; } /* get the key type and see if it is the type that we are looking for */ if (oidc_jwt_alg2kty(jwt) != jwk->kty) { oidc_debug(r, "skipping non matching kty=%d for kid=%s because it doesn't match requested kty=%d, kid=%s", jwk->kty, jwk->kid, oidc_jwt_alg2kty(jwt), jwt->header.kid); oidc_jwk_destroy(jwk); continue; } /* see if we were looking for a specific kid, if not we'll include any key that matches the type */ if ((jwt->header.kid == NULL) && (x5t == NULL)) { const char *use = json_string_value( json_object_get(elem, OIDC_JWK_USE)); if ((use != NULL) && (strcmp(use, OIDC_JWK_SIG) != 0)) { oidc_debug(r, "skipping key because of non-matching \"%s\": \"%s\"", OIDC_JWK_USE, use); oidc_jwk_destroy(jwk); } else { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "no kid/x5t to match, include matching key type: %s", jwk_json); if (jwk->kid != NULL) apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk); else // can do this because we never remove anything from the list apr_hash_set(result, apr_psprintf(r->pool, "%d", apr_hash_count(result)), APR_HASH_KEY_STRING, jwk); } continue; } /* we are looking for a specific kid, get the kid from the current element */ /* compare the requested kid against the current element */ if ((jwt->header.kid != NULL) && (jwk->kid != NULL) && (apr_strnatcmp(jwt->header.kid, jwk->kid) == 0)) { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "found matching kid: \"%s\" for jwk: %s", jwt->header.kid, jwk_json); apr_hash_set(result, jwt->header.kid, APR_HASH_KEY_STRING, jwk); break; } /* we are looking for a specific x5t, get the x5t from the current element */ char *s_x5t = NULL; oidc_json_object_get_string(r->pool, elem, OIDC_JWK_X5T, &s_x5t, NULL); /* compare the requested thumbprint against the current element */ if ((s_x5t != NULL) && (x5t != NULL) && (apr_strnatcmp(x5t, s_x5t) == 0)) { oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err); oidc_debug(r, "found matching %s: \"%s\" for jwk: %s", OIDC_JWK_X5T, x5t, jwk_json); apr_hash_set(result, x5t, APR_HASH_KEY_STRING, jwk); break; } /* the right key type but no matching kid/x5t */ oidc_jwk_destroy(jwk); } return rc; } /* * get the keys from the (possibly cached) set of JWKs on the jwk_uri that corresponds with the key specified in the header */ apr_byte_t oidc_proto_get_keys_from_jwks_uri(request_rec *r, oidc_cfg *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, apr_hash_t *keys, apr_byte_t *force_refresh) { json_t *j_jwks = NULL; /* get the set of JSON Web Keys for this provider (possibly by downloading them from the specified provider->jwk_uri) */ oidc_metadata_jwks_get(r, cfg, jwks_uri, &j_jwks, force_refresh); if (j_jwks == NULL) { oidc_error(r, "could not %s JSON Web Keys", *force_refresh ? "refresh" : "get"); return FALSE; } /* * get the key corresponding to the kid from the header, referencing the key that * was used to sign this message (or get all keys in case no kid was set) * * we don't check the error return value because we'll treat "error" in the same * way as "key not found" i.e. by refreshing the keys from the JWKs URI if not * already done */ oidc_proto_get_key_from_jwks(r, jwt, j_jwks, keys); /* no need anymore for the parsed json_t contents, release the it */ json_decref(j_jwks); /* if we've got no keys and we did not do a fresh download, then the cache may be stale */ if ((apr_hash_count(keys) < 1) && (*force_refresh == FALSE)) { /* we did not get a key, but we have not refreshed the JWKs from the jwks_uri yet */ oidc_warn(r, "could not find a key in the cached JSON Web Keys, doing a forced refresh in case keys were rolled over"); /* get the set of JSON Web Keys forcing a fresh download from the specified JWKs URI */ *force_refresh = TRUE; return oidc_proto_get_keys_from_jwks_uri(r, cfg, jwt, jwks_uri, keys, force_refresh); } oidc_debug(r, "returning %d key(s) obtained from the (possibly cached) JWKs URI", apr_hash_count(keys)); return TRUE; } /* * verify the signature on a JWT using the dynamically obtained and statically configured keys */ apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, apr_hash_t *static_keys) { oidc_jose_error_t err; apr_hash_t *dynamic_keys = apr_hash_make(r->pool); /* see if we've got a JWKs URI set for signature validation with dynamically obtained asymmetric keys */ if (jwks_uri->url == NULL) { oidc_debug(r, "\"jwks_uri\" is not set, signature validation will only be performed against statically configured keys"); /* the JWKs URI was provided, but let's see if it makes sense to pull down keys, i.e. if it is an asymmetric signature */ } /*else if (oidc_jose_signature_is_hmac(r->pool, jwt)) { oidc_debug(r, "\"jwks_uri\" is set, but the JWT has a symmetric signature so we won't pull/use keys from there"); } */else { apr_byte_t force_refresh = FALSE; /* get the key from the JWKs that corresponds with the key specified in the header */ if (oidc_proto_get_keys_from_jwks_uri(r, cfg, jwt, jwks_uri, dynamic_keys, &force_refresh) == FALSE) { oidc_jwk_list_destroy(r->pool, dynamic_keys); return FALSE; } } /* do the actual JWS verification with the locally and remotely provided key material */ // TODO: now static keys "win" if the same `kid` was used in both local and remote key sets if (oidc_jwt_verify(r->pool, jwt, oidc_util_merge_key_sets(r->pool, static_keys, dynamic_keys), &err) == FALSE) { oidc_error(r, "JWT signature verification failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_list_destroy(r->pool, dynamic_keys); return FALSE; } oidc_debug(r, "JWT signature verification with algorithm \"%s\" was successful", jwt->header.alg); oidc_jwk_list_destroy(r->pool, dynamic_keys); return TRUE; } /* * return the compact-encoded JWT header contents */ char *oidc_proto_peek_jwt_header(request_rec *r, const char *compact_encoded_jwt, char **alg) { char *input = NULL, *result = NULL; char *p = strstr(compact_encoded_jwt ? compact_encoded_jwt : "", "."); if (p == NULL) { oidc_warn(r, "could not parse first element separated by \".\" from input"); return NULL; } input = apr_pstrmemdup(r->pool, compact_encoded_jwt, strlen(compact_encoded_jwt) - strlen(p)); if (oidc_base64url_decode(r->pool, &result, input) <= 0) { oidc_warn(r, "oidc_base64url_decode returned an error"); return NULL; } if (alg) { json_t *json = NULL; oidc_util_decode_json_object(r, result, &json); if (json) *alg = apr_pstrdup(r->pool, json_string_value(json_object_get(json, CJOSE_HDR_ALG))); json_decref(json); } return result; } /* * check whether the provided string is a valid id_token and return its parsed contents */ apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow) { char *alg = NULL; oidc_debug(r, "enter: id_token header=%s", oidc_proto_peek_jwt_header(r, id_token, &alg)); char buf[APR_RFC822_DATE_LEN + 1]; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; if (oidc_jwt_parse(r->pool, id_token, jwt, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully parsed (and possibly decrypted) JWT with header=%s, and payload=%s", (*jwt)->header.value.str, (*jwt)->payload.value.str); // make signature validation exception for 'code' flow and the algorithm NONE if (is_code_flow == FALSE || strcmp((*jwt)->header.alg, "none") != 0) { jwk = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; oidc_jwks_uri_t jwks_uri = { provider->jwks_uri, provider->jwks_refresh_interval, provider->ssl_validate_server }; if (oidc_proto_jwt_verify(r, cfg, *jwt, &jwks_uri, oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) { oidc_error(r, "id_token signature could not be validated, aborting"); oidc_jwt_destroy(*jwt); *jwt = NULL; oidc_jwk_destroy(jwk); return FALSE; } oidc_jwk_destroy(jwk); } /* this is where the meat is */ if (oidc_proto_validate_idtoken(r, provider, *jwt, nonce) == FALSE) { oidc_error(r, "id_token payload could not be validated, aborting"); oidc_jwt_destroy(*jwt); *jwt = NULL; return FALSE; } /* log our results */ apr_rfc822_date(buf, apr_time_from_sec((*jwt)->payload.exp)); oidc_debug(r, "valid id_token for user \"%s\" expires: [%s], in %ld secs from now)", (*jwt)->payload.sub, buf, (long)((*jwt)->payload.exp - apr_time_sec(apr_time_now()))); /* since we've made it so far, we may as well say it is a valid id_token */ return TRUE; } /* * check that the access_token type is supported */ static apr_byte_t oidc_proto_validate_token_type(request_rec *r, oidc_provider_t *provider, const char *token_type) { /* we only support bearer/Bearer */ if ((token_type != NULL) && (apr_strnatcasecmp(token_type, OIDC_PROTO_BEARER) != 0) && (provider->userinfo_endpoint_url != NULL)) { oidc_error(r, "token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal with \"%s\" authentication against a UserInfo endpoint!", token_type, provider->userinfo_endpoint_url, provider->issuer, OIDC_PROTO_BEARER); return FALSE; } return TRUE; } /* * setup for an endpoint call without authentication */ static apr_byte_t oidc_proto_endpoint_auth_none(request_rec *r, const char *client_id, apr_table_t *params) { oidc_debug(r, "no client secret is configured; calling the token endpoint without client authentication; only public clients are supported"); apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id); return TRUE; } /* * setup for an endpoint call with HTTP Basic authentication */ static apr_byte_t oidc_proto_endpoint_auth_basic(request_rec *r, const char *client_id, const char *client_secret, char **basic_auth_str) { oidc_debug(r, "enter"); if (client_secret == NULL) { oidc_error(r, "no client secret is configured"); return FALSE; } *basic_auth_str = apr_psprintf(r->pool, "%s:%s", client_id, client_secret); return TRUE; } /* * setup for an endpoint call with authentication in POST parameters */ static apr_byte_t oidc_proto_endpoint_auth_post(request_rec *r, const char *client_id, const char *client_secret, apr_table_t *params) { oidc_debug(r, "enter"); if (client_secret == NULL) { oidc_error(r, "no client secret is configured"); return FALSE; } apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id); apr_table_set(params, OIDC_PROTO_CLIENT_SECRET, client_secret); return TRUE; } #define OIDC_PROTO_ASSERTION_JTI_LEN 16 /* * helper function to create a JWT assertion for endpoint authentication */ static apr_byte_t oidc_proto_jwt_create(request_rec *r, const char *client_id, const char *audience, oidc_jwt_t **out) { *out = oidc_jwt_new(r->pool, TRUE, TRUE); oidc_jwt_t *jwt = *out; char *jti = NULL; oidc_proto_generate_random_string(r, &jti, OIDC_PROTO_ASSERTION_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, json_string(client_id)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_SUB, json_string(client_id)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, json_string(audience)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP, json_integer(apr_time_sec(apr_time_now()) + 60)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); return TRUE; } /* * helper function to add a JWT assertion to the HTTP request as endpoint authentication */ static apr_byte_t oidc_proto_jwt_sign_and_add(request_rec *r, apr_table_t *params, oidc_jwt_t *jwt, oidc_jwk_t *jwk) { oidc_jose_error_t err; if (oidc_jwt_sign(r->pool, jwt, jwk, &err) == FALSE) { oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } char *cser = oidc_jwt_serialize(r->pool, jwt, &err); if (cser == NULL) { oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } apr_table_setn(params, OIDC_PROTO_CLIENT_ASSERTION_TYPE, OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER); apr_table_set(params, OIDC_PROTO_CLIENT_ASSERTION, cser); return TRUE; } #define OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG CJOSE_HDR_ALG_HS256 static apr_byte_t oidc_proto_endpoint_auth_client_secret_jwt(request_rec *r, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; oidc_debug(r, "enter"); if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE) return FALSE; oidc_jwk_t *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, (const unsigned char *) client_secret, strlen(client_secret), FALSE, &err); if (jwk == NULL) { oidc_error(r, "parsing of client secret into JWK failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); return FALSE; } jwt->header.alg = apr_pstrdup(r->pool, OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG); oidc_proto_jwt_sign_and_add(r, params, jwt, jwk); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return TRUE; } static apr_byte_t oidc_proto_endpoint_access_token_bearer(request_rec *r, oidc_cfg *cfg, apr_table_t *params, char **bearer_auth_str) { const char *token = strcmp(cfg->oauth.introspection_client_auth_bearer_token, "") == 0 ? apr_table_get(params, cfg->oauth.introspection_token_param_name) : cfg->oauth.introspection_client_auth_bearer_token; *bearer_auth_str = apr_psprintf(r->pool, "%s", token); return TRUE; } #define OIDC_PROTO_JWT_ASSERTION_ASYMMETRIC_ALG CJOSE_HDR_ALG_RS256 static apr_byte_t oidc_proto_endpoint_auth_private_key_jwt(request_rec *r, oidc_cfg *cfg, const char *client_id, const char *audience, apr_table_t *params) { oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; oidc_debug(r, "enter"); if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE) return FALSE; if (cfg->private_keys == NULL) { oidc_error(r, "no private keys have been configured to use for private_key_jwt client authentication (" OIDCPrivateKeyFiles ")"); oidc_jwt_destroy(jwt); return FALSE; } apr_ssize_t klen = 0; apr_hash_index_t *hi = apr_hash_first(r->pool, cfg->private_keys); apr_hash_this(hi, (const void **) &jwt->header.kid, &klen, (void **) &jwk); jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_RS256); oidc_proto_jwt_sign_and_add(r, params, jwt, jwk); oidc_jwt_destroy(jwt); return TRUE; } apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params, char **basic_auth_str, char **bearer_auth_str) { if (cfg->oauth.introspection_client_auth_bearer_token != NULL) return oidc_proto_endpoint_access_token_bearer(r, cfg, params, bearer_auth_str); oidc_debug(r, "token_endpoint_auth=%s", token_endpoint_auth); if (client_id == NULL) { oidc_debug(r, "no client ID set: assume we don't need to authenticate"); return TRUE; } // default is client_secret_basic, but only if a client_secret is set, // otherwise we are a public client if ((token_endpoint_auth == NULL) && (client_secret != NULL)) token_endpoint_auth = OIDC_PROTO_CLIENT_SECRET_BASIC; if ((token_endpoint_auth == NULL) || (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_ENDPOINT_AUTH_NONE) == 0)) return oidc_proto_endpoint_auth_none(r, client_id, params); // if no client_secret is set and we don't authenticate using private_key_jwt, // we can only be a public client since the other methods require a client_secret if ((client_secret == NULL) && (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_PRIVATE_KEY_JWT) != 0)) { oidc_debug(r, "no client secret set and not using private_key_jwt, assume we are a public client"); return oidc_proto_endpoint_auth_none(r, client_id, params); } if (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_BASIC) == 0) return oidc_proto_endpoint_auth_basic(r, client_id, client_secret, basic_auth_str); if (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_POST) == 0) return oidc_proto_endpoint_auth_post(r, client_id, client_secret, params); if (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_JWT) == 0) return oidc_proto_endpoint_auth_client_secret_jwt(r, client_id, client_secret, audience, params); if (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_PRIVATE_KEY_JWT) == 0) return oidc_proto_endpoint_auth_private_key_jwt(r, cfg, client_id, audience, params); oidc_error(r, "uhm, shouldn't be here..."); return FALSE; } /* * send a code/refresh request to the token endpoint and return the parsed contents */ static apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, apr_table_t *params, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { char *response = NULL; char *basic_auth = NULL; char *bearer_auth = NULL; /* add the token endpoint authentication credentials */ if (oidc_proto_token_endpoint_auth(r, cfg, provider->token_endpoint_auth, provider->client_id, provider->client_secret, provider->token_endpoint_url, params, &basic_auth, &bearer_auth) == FALSE) return FALSE; /* add any configured extra static parameters to the token endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, provider->token_endpoint_params); /* send the refresh request to the token endpoint */ if (oidc_util_http_post_form(r, provider->token_endpoint_url, params, basic_auth, bearer_auth, provider->ssl_validate_server, &response, cfg->http_timeout_long, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), oidc_util_get_full_path(r->pool, provider->token_endpoint_tls_client_cert), oidc_util_get_full_path(r->pool, provider->token_endpoint_tls_client_key)) == FALSE) { oidc_warn(r, "error when calling the token endpoint (%s)", provider->token_endpoint_url); return FALSE; } /* check for errors, the response itself will have been logged already */ json_t *result = NULL; if (oidc_util_decode_json_and_check_error(r, response, &result) == FALSE) return FALSE; /* get the id_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ID_TOKEN, id_token, NULL); /* get the access_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ACCESS_TOKEN, access_token, NULL); /* get the token type from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_TOKEN_TYPE, token_type, NULL); /* check the new token type */ if (token_type != NULL) { if (oidc_proto_validate_token_type(r, provider, *token_type) == FALSE) { oidc_warn(r, "access token type did not validate, dropping it"); *access_token = NULL; } } /* get the expires_in value */ oidc_json_object_get_int(r->pool, result, OIDC_PROTO_EXPIRES_IN, expires_in, -1); /* get the refresh_token from the parsed response */ oidc_json_object_get_string(r->pool, result, OIDC_PROTO_REFRESH_TOKEN, refresh_token, NULL); json_decref(result); return TRUE; } /* * resolves the code received from the OP in to an id_token, access_token and refresh_token */ static apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *code, const char *code_verifier, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token, const char *state) { oidc_debug(r, "enter"); /* assemble the parameters for a call to the token endpoint */ apr_table_t *params = apr_table_make(r->pool, 5); apr_table_setn(params, OIDC_PROTO_GRANT_TYPE, OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE); apr_table_setn(params, OIDC_PROTO_CODE, code); apr_table_set(params, OIDC_PROTO_REDIRECT_URI, oidc_get_redirect_uri_iss(r, cfg, provider)); if (code_verifier) apr_table_setn(params, OIDC_PROTO_CODE_VERIFIER, code_verifier); if (state) apr_table_setn(params, OIDC_PROTO_STATE, state); return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type, expires_in, refresh_token); } /* * refreshes the access_token/id_token /refresh_token received from the OP using the refresh_token */ apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { oidc_debug(r, "enter"); /* assemble the parameters for a call to the token endpoint */ apr_table_t *params = apr_table_make(r->pool, 5); apr_table_setn(params, OIDC_PROTO_GRANT_TYPE, OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN); apr_table_setn(params, OIDC_PROTO_REFRESH_TOKEN, rtoken); apr_table_setn(params, OIDC_PROTO_SCOPE, provider->scope); return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type, expires_in, refresh_token); } static apr_byte_t oidc_user_info_response_validate(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, char **response, json_t **claims, char **userinfo_jwt) { oidc_debug(r, "enter: userinfo_signed_response_alg=%s, userinfo_encrypted_response_alg=%s, userinfo_encrypted_response_enc=%s", provider->userinfo_signed_response_alg, provider->userinfo_encrypted_response_alg, provider->userinfo_encrypted_response_enc); char *alg = NULL; if ((provider->userinfo_signed_response_alg != NULL) || (provider->userinfo_encrypted_response_alg != NULL) || (provider->userinfo_encrypted_response_enc != NULL)) { oidc_debug(r, "JWT header=%s", oidc_proto_peek_jwt_header(r, *response, &alg)); } oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwt = NULL; char *payload = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE) return FALSE; if (provider->userinfo_encrypted_response_alg != NULL) { if (oidc_jwe_decrypt(r->pool, *response, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), &payload, &err, TRUE) == FALSE) { oidc_error(r, "oidc_jwe_decrypt failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwk_destroy(jwk); return FALSE; } else { oidc_debug(r, "successfully decrypted JWE returned from userinfo endpoint: %s", payload); *response = payload; } } if (provider->userinfo_signed_response_alg != NULL) { if (oidc_jwt_parse(r->pool, *response, &jwt, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), &err) == FALSE) { oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err)); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return FALSE; } oidc_debug(r, "successfully parsed JWT with header=%s, and payload=%s", jwt->header.value.str, jwt->payload.value.str); oidc_jwk_destroy(jwk); jwk = NULL; if (oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL, TRUE, &jwk) == FALSE) return FALSE; oidc_jwks_uri_t jwks_uri = { provider->jwks_uri, provider->jwks_refresh_interval, provider->ssl_validate_server }; if (oidc_proto_jwt_verify(r, cfg, jwt, &jwks_uri, oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) { oidc_error(r, "JWT signature could not be validated, aborting"); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return FALSE; } oidc_jwk_destroy(jwk); oidc_debug(r, "successfully verified signed JWT returned from userinfo endpoint: %s", jwt->payload.value.str); *userinfo_jwt = apr_pstrdup(r->pool, *response); *claims = json_deep_copy(jwt->payload.value.json); *response = apr_pstrdup(r->pool, jwt->payload.value.str); oidc_jwt_destroy(jwt); return TRUE; } oidc_jwk_destroy(jwk); return oidc_util_decode_json_and_check_error(r, *response, claims); } #define OIDC_COMPOSITE_CLAIM_NAMES "_claim_names" #define OIDC_COMPOSITE_CLAIM_SOURCES "_claim_sources" #define OIDC_COMPOSITE_CLAIM_JWT "JWT" #define OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN OIDC_PROTO_ACCESS_TOKEN #define OIDC_COMPOSITE_CLAIM_ENDPOINT "endpoint" static apr_byte_t oidc_proto_resolve_composite_claims(request_rec *r, oidc_cfg *cfg, json_t *claims) { const char *key; json_t *value; void *iter; json_t *sources, *names, *decoded; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_debug(r, "enter"); names = json_object_get(claims, OIDC_COMPOSITE_CLAIM_NAMES); if ((names == NULL) || (!json_is_object(names))) return FALSE; sources = json_object_get(claims, OIDC_COMPOSITE_CLAIM_SOURCES); if ((sources == NULL) || (!json_is_object(sources))) { oidc_debug(r, "%s found, but no %s found", OIDC_COMPOSITE_CLAIM_NAMES, OIDC_COMPOSITE_CLAIM_SOURCES); return FALSE; } decoded = json_object(); iter = json_object_iter(sources); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); if ((value != NULL) && (json_is_object(value))) { json_t *jwt = json_object_get(value, OIDC_COMPOSITE_CLAIM_JWT); char *s_json = NULL; if ((jwt != NULL) && (json_is_string(jwt))) { s_json = apr_pstrdup(r->pool, json_string_value(jwt)); } else { const char *access_token = json_string_value( json_object_get(value, OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN)); const char *endpoint = json_string_value( json_object_get(value, OIDC_COMPOSITE_CLAIM_ENDPOINT)); if ((access_token != NULL) && (endpoint != NULL)) { oidc_util_http_get(r, endpoint, NULL, NULL, access_token, cfg->provider.ssl_validate_server, &s_json, cfg->http_timeout_long, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL); } } if ((s_json != NULL) && (strcmp(s_json, "") != 0)) { oidc_jwt_t *jwt = NULL; if (oidc_jwt_parse(r->pool, s_json, &jwt, oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk), &err) == FALSE) { oidc_error(r, "could not parse JWT from aggregated claim \"%s\": %s", key, oidc_jose_e2s(r->pool, err)); } else { json_t *v = json_object_get(decoded, key); if (v == NULL) { v = json_object(); json_object_set_new(decoded, key, v); } oidc_util_json_merge(r, jwt->payload.value.json, v); } oidc_jwt_destroy(jwt); } } iter = json_object_iter_next(sources, iter); } iter = json_object_iter(names); while (iter) { key = json_object_iter_key(iter); const char *s_value = json_string_value(json_object_iter_value(iter)); if (s_value != NULL) { oidc_debug(r, "processing: %s: %s", key, s_value); json_t *values = json_object_get(decoded, s_value); if (values != NULL) { json_object_set(claims, key, json_object_get(values, key)); } else { oidc_warn(r, "no values for source \"%s\" found", s_value); } } else { oidc_warn(r, "no string value found for claim \"%s\"", key); } iter = json_object_iter_next(names, iter); } json_object_del(claims, OIDC_COMPOSITE_CLAIM_NAMES); json_object_del(claims, OIDC_COMPOSITE_CLAIM_SOURCES); json_decref(decoded); return TRUE; } /* * get claims from the OP UserInfo endpoint using the provided access_token */ apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt) { oidc_debug(r, "enter, endpoint=%s, access_token=%s", provider->userinfo_endpoint_url, access_token); /* get the JSON response */ if (provider->userinfo_token_method == OIDC_USER_INFO_TOKEN_METHOD_HEADER) { if (oidc_util_http_get(r, provider->userinfo_endpoint_url, NULL, NULL, access_token, provider->ssl_validate_server, response, cfg->http_timeout_long, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) return FALSE; } else if (provider->userinfo_token_method == OIDC_USER_INFO_TOKEN_METHOD_POST) { apr_table_t *params = apr_table_make(r->pool, 4); apr_table_setn(params, OIDC_PROTO_ACCESS_TOKEN, access_token); if (oidc_util_http_post_form(r, provider->userinfo_endpoint_url, params, NULL, access_token, provider->ssl_validate_server, response, cfg->http_timeout_long, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) return FALSE; } else { oidc_error(r, "unsupported userinfo token presentation method: %d", provider->userinfo_token_method); return FALSE; } json_t *claims = NULL; if (oidc_user_info_response_validate(r, cfg, provider, response, &claims, userinfo_jwt) == FALSE) return FALSE; if (oidc_proto_resolve_composite_claims(r, cfg, claims) == TRUE) *response = oidc_util_encode_json_object(r, claims, JSON_PRESERVE_ORDER | JSON_COMPACT); char *user_info_sub = NULL; oidc_jose_get_string(r->pool, claims, OIDC_CLAIM_SUB, FALSE, &user_info_sub, NULL); oidc_debug(r, "id_token_sub=%s, user_info_sub=%s", id_token_sub, user_info_sub); if ((id_token_sub != NULL) && (user_info_sub != NULL)) { if (apr_strnatcmp(id_token_sub, user_info_sub) != 0) { oidc_error(r, "\"%s\" claim (\"%s\") returned from userinfo endpoint does not match the one in the id_token (\"%s\")", OIDC_CLAIM_SUB, user_info_sub, id_token_sub); json_decref(claims); return FALSE; } } json_decref(claims); return TRUE; } /* * based on a resource perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata */ static apr_byte_t oidc_proto_webfinger_discovery(request_rec *r, oidc_cfg *cfg, const char *resource, const char *domain, char **issuer) { const char *url = apr_psprintf(r->pool, "https://%s/.well-known/webfinger", domain); apr_table_t *params = apr_table_make(r->pool, 1); apr_table_setn(params, "resource", resource); apr_table_setn(params, "rel", "http://openid.net/specs/connect/1.0/issuer"); char *response = NULL; if (oidc_util_http_get(r, url, params, NULL, NULL, cfg->provider.ssl_validate_server, &response, cfg->http_timeout_short, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) { /* errors will have been logged by now */ return FALSE; } /* decode and see if it is not an error response somehow */ json_t *j_response = NULL; if (oidc_util_decode_json_and_check_error(r, response, &j_response) == FALSE) return FALSE; /* get the links parameter */ json_t *j_links = json_object_get(j_response, "links"); if ((j_links == NULL) || (!json_is_array(j_links))) { oidc_error(r, "response JSON object did not contain a \"links\" array"); json_decref(j_response); return FALSE; } /* get the one-and-only object in the "links" array */ json_t *j_object = json_array_get(j_links, 0); if ((j_object == NULL) || (!json_is_object(j_object))) { oidc_error(r, "response JSON object did not contain a JSON object as the first element in the \"links\" array"); json_decref(j_response); return FALSE; } /* get the href from that object, which is the issuer value */ json_t *j_href = json_object_get(j_object, "href"); if ((j_href == NULL) || (!json_is_string(j_href))) { oidc_error(r, "response JSON object did not contain a \"href\" element in the first \"links\" array object"); json_decref(j_response); return FALSE; } /* check that the link is on secure HTTPs */ if (oidc_valid_url(r->pool, json_string_value(j_href), "https") != NULL) { oidc_error(r, "response JSON object contains an \"href\" value that is not a valid \"https\" URL: %s", json_string_value(j_href)); json_decref(j_response); return FALSE; } *issuer = apr_pstrdup(r->pool, json_string_value(j_href)); oidc_debug(r, "returning issuer \"%s\" for resource \"%s\" after doing successful webfinger-based discovery", *issuer, resource); json_decref(j_response); return TRUE; } /* * based on an account name, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata */ apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, char **issuer) { // TODO: maybe show intermediate/progress screen "discovering..." oidc_debug(r, "enter, acct=%s", acct); const char *resource = apr_psprintf(r->pool, "acct:%s", acct); const char *domain = strrchr(acct, OIDC_CHAR_AT); if (domain == NULL) { oidc_error(r, "invalid account name"); return FALSE; } domain++; return oidc_proto_webfinger_discovery(r, cfg, resource, domain, issuer); } /* * based on user identifier URL, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata */ apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg, const char *url, char **issuer) { oidc_debug(r, "enter, url=%s", url); apr_uri_t uri; apr_uri_parse(r->pool, url, &uri); char *domain = uri.hostname; if (uri.port_str != NULL) domain = apr_psprintf(r->pool, "%s:%s", domain, uri.port_str); return oidc_proto_webfinger_discovery(r, cfg, url, domain, issuer); } int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c) { oidc_debug(r, "enter"); const char *java_script = " \n"; const char *html_body = "

Submitting...

\n" "
\n" "

\n" " \n" "

\n" "
\n"; return oidc_util_html_send(r, "Submitting...", java_script, "postOnLoad", html_body, DONE); } /* * check a provided hash value (at_hash|c_hash) against a corresponding hash calculated for a specified value and algorithm */ static apr_byte_t oidc_proto_validate_hash(request_rec *r, const char *alg, const char *hash, const char *value, const char *type) { char *calc = NULL; unsigned int calc_len = 0; unsigned int hash_len = oidc_jose_hash_length(alg) / 2; oidc_jose_error_t err; /* hash the provided access_token */ if (oidc_jose_hash_string(r->pool, alg, value, &calc, &calc_len, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_string failed: %s", oidc_jose_e2s(r->pool, err)); return FALSE; } /* calculate the base64url-encoded value of the hash */ char *decoded = NULL; unsigned int decoded_len = oidc_base64url_decode(r->pool, &decoded, hash); if (decoded_len <= 0) { oidc_error(r, "oidc_base64url_decode returned an error"); return FALSE; } oidc_debug(r, "hash_len=%d, decoded_len=%d, calc_len=%d", hash_len, decoded_len, calc_len); /* compare the calculated hash against the provided hash */ if ((decoded_len < hash_len) || (calc_len < hash_len) || (memcmp(decoded, calc, hash_len) != 0)) { oidc_error(r, "provided \"%s\" hash value (%s) does not match the calculated value", type, hash); return FALSE; } oidc_debug(r, "successfully validated the provided \"%s\" hash value (%s) against the calculated value", type, hash); return TRUE; } /* * check a hash value in the id_token against the corresponding hash calculated over a provided value */ static apr_byte_t oidc_proto_validate_hash_value(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *value, const char *key, apr_array_header_t *required_for_flows) { /* * get the hash value from the id_token */ char *hash = NULL; oidc_jose_get_string(r->pool, jwt->payload.value.json, key, FALSE, &hash, NULL); /* * check if the hash was present */ if (hash == NULL) { /* no hash..., now see if the flow required it */ int i; for (i = 0; i < required_for_flows->nelts; i++) { if (oidc_util_spaced_string_equals(r->pool, response_type, ((const char**) required_for_flows->elts)[i])) { oidc_warn(r, "flow is \"%s\", but no %s found in id_token", response_type, key); return FALSE; } } /* no hash but it was not required anyway */ return TRUE; } /* * we have a hash, validate it and return the result */ return oidc_proto_validate_hash(r, jwt->header.alg, hash, value, key); } /* * check the c_hash value in the id_token against the code */ apr_byte_t oidc_proto_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *code) { apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2, sizeof(const char*)); *(const char**) apr_array_push(required_for_flows) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; *(const char**) apr_array_push(required_for_flows) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, code, OIDC_CLAIM_C_HASH, required_for_flows) == FALSE) { oidc_error(r, "could not validate code against \"%s\" claim value", OIDC_CLAIM_C_HASH); return FALSE; } return TRUE; } /* * check the at_hash value in the id_token against the access_token */ apr_byte_t oidc_proto_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *access_token) { apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2, sizeof(const char*)); *(const char**) apr_array_push(required_for_flows) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; *(const char**) apr_array_push(required_for_flows) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, access_token, OIDC_CLAIM_AT_HASH, required_for_flows) == FALSE) { oidc_error(r, "could not validate access token against \"%s\" claim value", OIDC_CLAIM_AT_HASH); return FALSE; } return TRUE; } /* * return the supported flows */ apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 6, sizeof(const char*)); *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_CODE; *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN; *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN; *(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; return result; } /* * check if a particular OpenID Connect flow is supported */ apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow) { apr_array_header_t *flows = oidc_proto_supported_flows(pool); int i; for (i = 0; i < flows->nelts; i++) { if (oidc_util_spaced_string_equals(pool, flow, ((const char**) flows->elts)[i])) return TRUE; } return FALSE; } /* * check the required parameters for the various flows after resolving the authorization code */ static apr_byte_t oidc_proto_validate_code_response(request_rec *r, const char *response_type, char *id_token, char *access_token, char *token_type) { oidc_debug(r, "enter"); /* * check id_token parameter */ if (!oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { if (id_token == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } } else { if (id_token != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is an \"%s\" parameter in the code response that will be dropped", response_type, OIDC_PROTO_ID_TOKEN); } } /* * check access_token parameter */ if (!oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_TOKEN)) { if (access_token == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } if (token_type == NULL) { oidc_error(r, "requested flow is \"%s\" but no \"%s\" parameter found in the code response", response_type, OIDC_PROTO_TOKEN_TYPE); return FALSE; } } else { if (access_token != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is an \"%s\" parameter in the code response that will be dropped", response_type, OIDC_PROTO_ACCESS_TOKEN); } if (token_type != NULL) { oidc_warn(r, "requested flow is \"%s\" but there is a \"%s\" parameter in the code response that will be dropped", response_type, OIDC_PROTO_TOKEN_TYPE); } } return TRUE; } /* * validate the response parameters provided by the OP against the requested response type */ static apr_byte_t oidc_proto_validate_response_type(request_rec *r, const char *requested_response_type, const char *code, const char *id_token, const char *access_token) { if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_CODE)) { if (code == NULL) { oidc_error(r, "the requested response type was (%s) but the response does not contain a \"%s\" parameter", requested_response_type, OIDC_PROTO_CODE); return FALSE; } } else if (code != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains a \"%s\" parameter", requested_response_type, OIDC_PROTO_CODE); return FALSE; } if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) { if (id_token == NULL) { oidc_error(r, "the requested response type was (%s) but the response does not contain an \"%s\" parameter", requested_response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } } else if (id_token != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains an \"%s\" parameter", requested_response_type, OIDC_PROTO_ID_TOKEN); return FALSE; } if (oidc_util_spaced_string_contains(r->pool, requested_response_type, OIDC_PROTO_RESPONSE_TYPE_TOKEN)) { if (access_token == NULL) { oidc_error(r, "the requested response type was (%s) but the response does not contain an \"%s\" parameter", requested_response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } } else if (access_token != NULL) { oidc_error(r, "the requested response type was (%s) but the response contains an \"%s\" parameter", requested_response_type, OIDC_PROTO_ACCESS_TOKEN); return FALSE; } return TRUE; } /* * validate the response mode used by the OP against the requested response mode */ static apr_byte_t oidc_proto_validate_response_mode(request_rec *r, oidc_proto_state_t *proto_state, const char *response_mode, const char *default_response_mode) { const char *requested_response_mode = oidc_proto_state_get_response_mode( proto_state); if (requested_response_mode == NULL) requested_response_mode = default_response_mode; if (apr_strnatcmp(requested_response_mode, response_mode) != 0) { oidc_error(r, "requested response mode (%s) does not match the response mode used by the OP (%s)", requested_response_mode, response_mode); return FALSE; } return TRUE; } /* * validate the client_id/iss provided by the OP against the client_id/iss registered with the provider that the request was sent to */ static apr_byte_t oidc_proto_validate_issuer_client_id(request_rec *r, const char *configured_issuer, const char *response_issuer, const char *configured_client_id, const char *response_client_id) { if (response_issuer != NULL) { if (apr_strnatcmp(configured_issuer, response_issuer) != 0) { oidc_error(r, "configured issuer (%s) does not match the issuer provided in the response by the OP (%s)", configured_issuer, response_issuer); return FALSE; } } if (response_client_id != NULL) { if (apr_strnatcmp(configured_client_id, response_client_id) != 0) { oidc_error(r, "configured client_id (%s) does not match the client_id provided in the response by the OP (%s)", configured_client_id, response_client_id); return FALSE; } } oidc_debug(r, "iss and/or client_id matched OK: %s, %s, %s, %s", response_issuer, configured_issuer, response_client_id, configured_client_id); return TRUE; } /* * helper function to validate both the response type and the response mode in a single function call */ static apr_byte_t oidc_proto_validate_response_type_mode_issuer(request_rec *r, const char *requested_response_type, apr_table_t *params, oidc_proto_state_t *proto_state, const char *response_mode, const char *default_response_mode, const char *issuer, const char *c_client_id) { const char *code = apr_table_get(params, OIDC_PROTO_CODE); const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN); const char *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN); const char *iss = apr_table_get(params, OIDC_PROTO_ISS); const char *client_id = apr_table_get(params, OIDC_PROTO_CLIENT_ID); if (oidc_proto_validate_issuer_client_id(r, issuer, iss, c_client_id, client_id) == FALSE) return FALSE; if (oidc_proto_validate_response_type(r, requested_response_type, code, id_token, access_token) == FALSE) return FALSE; if (oidc_proto_validate_response_mode(r, proto_state, response_mode, default_response_mode) == FALSE) return FALSE; return TRUE; } /* * parse and id_token and check the c_hash if the code is provided */ static apr_byte_t oidc_proto_parse_idtoken_and_validate_code(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, const char *response_type, apr_table_t *params, oidc_jwt_t **jwt, apr_byte_t must_validate_code) { const char *code = apr_table_get(params, OIDC_PROTO_CODE); const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN); apr_byte_t is_code_flow = (oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_CODE) == TRUE) && (oidc_util_spaced_string_contains(r->pool, response_type, OIDC_PROTO_RESPONSE_TYPE_IDTOKEN) == FALSE); const char *nonce = oidc_proto_state_get_nonce(proto_state); if (oidc_proto_parse_idtoken(r, c, provider, id_token, nonce, jwt, is_code_flow) == FALSE) return FALSE; if ((must_validate_code == TRUE) && (oidc_proto_validate_code(r, provider, *jwt, response_type, code) == FALSE)) return FALSE; return TRUE; } /* * resolve the code against the token endpoint and validate the response that is returned by the OP */ static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r, oidc_cfg *c, oidc_provider_t *provider, const char *response_type, apr_table_t *params, oidc_proto_state_t *proto_state) { char *id_token = NULL; char *access_token = NULL; char *token_type = NULL; int expires_in = -1; char *refresh_token = NULL; char *code_verifier = NULL; if (provider->pkce != NULL) provider->pkce->verifier(r, oidc_proto_state_get_pkce_state(proto_state), &code_verifier); const char *state = oidc_proto_state_get_state(proto_state); if (oidc_proto_resolve_code(r, c, provider, apr_table_get(params, OIDC_PROTO_CODE), code_verifier, &id_token, &access_token, &token_type, &expires_in, &refresh_token, state) == FALSE) { oidc_error(r, "failed to resolve the code"); return FALSE; } if (oidc_proto_validate_code_response(r, response_type, id_token, access_token, token_type) == FALSE) { oidc_error(r, "code response validation failed"); return FALSE; } /* don't override parameters that may already have been (rightfully) set in the authorization response */ if ((apr_table_get(params, OIDC_PROTO_ID_TOKEN) == NULL) && (id_token != NULL)) { apr_table_set(params, OIDC_PROTO_ID_TOKEN, id_token); } if ((apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN) == NULL) && (access_token != NULL)) { apr_table_set(params, OIDC_PROTO_ACCESS_TOKEN, access_token); if (token_type != NULL) apr_table_set(params, OIDC_PROTO_TOKEN_TYPE, token_type); if (expires_in != -1) apr_table_setn(params, OIDC_PROTO_EXPIRES_IN, apr_psprintf(r->pool, "%d", expires_in)); } /* refresh token should not have been set before */ if (refresh_token != NULL) { apr_table_set(params, OIDC_PROTO_REFRESH_TOKEN, refresh_token); } return TRUE; } /* * handle the "code id_token" response type */ apr_byte_t oidc_proto_authorization_response_code_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN; if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, provider->issuer, provider->client_id) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN); apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; return TRUE; } /* * handle the "code token" response type */ apr_byte_t oidc_proto_handle_authorization_response_code_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN; if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, provider->issuer, provider->client_id) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ID_TOKEN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, FALSE) == FALSE) return FALSE; return TRUE; } /* * handle the "code" response type */ apr_byte_t oidc_proto_handle_authorization_response_code(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE; if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_QUERY, provider->issuer, provider->client_id) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN); apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_ID_TOKEN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; /* * in this flow it is actually optional to check the code token against the c_hash */ if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; /* * in this flow it is actually optional to check the access token against the at_hash */ if ((apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN) != NULL) && (oidc_proto_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE)) return FALSE; return TRUE; } /* * helper function for implicit flows: shared code for "id_token token" and "id_token" */ static apr_byte_t oidc_proto_handle_implicit_flow(request_rec *r, oidc_cfg *c, const char *response_type, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params, proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT, provider->issuer, provider->client_id) == FALSE) return FALSE; if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider, response_type, params, jwt, TRUE) == FALSE) return FALSE; return TRUE; } /* * handle the "code id_token token" response type */ apr_byte_t oidc_proto_authorization_response_code_idtoken_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; if (oidc_proto_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE) return FALSE; /* clear parameters that should only be set from the token endpoint */ apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE) return FALSE; return TRUE; } /* * handle the "id_token token" response type */ apr_byte_t oidc_proto_handle_authorization_response_idtoken_token( request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; if (oidc_proto_validate_access_token(r, provider, *jwt, response_type, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE) return FALSE; /* clear parameters that should not be part of this flow */ apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); return TRUE; } /* * handle the "id_token" response type */ apr_byte_t oidc_proto_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) { oidc_debug(r, "enter"); static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN; if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state, provider, params, response_mode, jwt) == FALSE) return FALSE; /* clear parameters that should not be part of this flow */ apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE); apr_table_unset(params, OIDC_PROTO_EXPIRES_IN); apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN); return TRUE; } mod_auth_openidc-2.3.3/src/config.c0000644000076500000240000032354313203320522017060 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include #include #include #include #include #include #include "mod_auth_openidc.h" #include "parse.h" #define OPENSSL_THREAD_DEFINES #include #include #include #if (OPENSSL_VERSION_NUMBER < 0x01000000) #define OPENSSL_NO_THREADID #endif /* validate SSL server certificates by default */ #define OIDC_DEFAULT_SSL_VALIDATE_SERVER 1 /* default scope requested from the OP */ #define OIDC_DEFAULT_SCOPE "openid" /* default claim delimiter for multi-valued claims passed in a HTTP header */ #define OIDC_DEFAULT_CLAIM_DELIMITER "," /* default prefix for claim names being passed in HTTP headers */ #define OIDC_DEFAULT_CLAIM_PREFIX "OIDC_CLAIM_" /* default name for the claim that will contain the REMOTE_USER value for OpenID Connect protected paths */ #define OIDC_DEFAULT_CLAIM_REMOTE_USER "sub@" /* default name for the claim that will contain the REMOTE_USER value for OAuth 2.0 protected paths */ #define OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER "sub" /* default name of the session cookie */ #define OIDC_DEFAULT_COOKIE "mod_auth_openidc_session" /* default for the HTTP header name in which the remote user name is passed */ #define OIDC_DEFAULT_AUTHN_HEADER NULL /* scrub HTTP headers by default unless overridden (and insecure) */ #define OIDC_DEFAULT_SCRUB_REQUEST_HEADERS 1 /* default client_name the client uses for dynamic client registration */ #define OIDC_DEFAULT_CLIENT_NAME "OpenID Connect Apache Module (mod_auth_openidc)" /* timeouts in seconds for HTTP calls that may take a long time */ #define OIDC_DEFAULT_HTTP_TIMEOUT_LONG 60 /* timeouts in seconds for HTTP calls that should take a short time (registry/discovery related) */ #define OIDC_DEFAULT_HTTP_TIMEOUT_SHORT 5 /* default session storage type */ #define OIDC_DEFAULT_SESSION_TYPE OIDC_SESSION_TYPE_SERVER_CACHE /* default client-cookie chunking size */ #define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000 /* timeout in seconds after which state expires */ #define OIDC_DEFAULT_STATE_TIMEOUT 300 /* default session inactivity timeout */ #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300 /* default session max duration */ #define OIDC_DEFAULT_SESSION_MAX_DURATION 3600 * 8 /* default OpenID Connect authorization response type */ #define OIDC_DEFAULT_RESPONSE_TYPE OIDC_PROTO_CODE /* default duration in seconds after which retrieved JWS should be refreshed */ #define OIDC_DEFAULT_JWKS_REFRESH_INTERVAL 3600 /* default max cache size for shm */ #define OIDC_DEFAULT_CACHE_SHM_SIZE 500 /* default max cache entry size for shm: # value + # key + # overhead */ #define OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX 16384 + 512 + 17 /* minimum size of a cache entry */ #define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 17 /* for issued-at timestamp (iat) checking */ #define OIDC_DEFAULT_IDTOKEN_IAT_SLACK 600 /* for file-based caching: clean interval in seconds */ #define OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL 60 /* set httponly flag on cookies */ #define OIDC_DEFAULT_COOKIE_HTTPONLY 1 /* set Same-Site flag on cookies */ #define OIDC_DEFAULT_COOKIE_SAME_SITE 0 /* default cookie path */ #define OIDC_DEFAULT_COOKIE_PATH "/" /* default OAuth 2.0 introspection token parameter name */ #define OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME "token" /* default OAuth 2.0 introspection call HTTP method */ #define OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD OIDC_INTROSPECTION_METHOD_POST /* default OAuth 2.0 non-spec compliant introspection expiry claim name */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME OIDC_PROTO_EXPIRES_IN /* default OAuth 2.0 non-spec compliant introspection expiry claim format */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT OIDC_CLAIM_FORMAT_RELATIVE /* default OAuth 2.0 non-spec compliant introspection expiry claim required */ #define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED TRUE /* default refresh interval in seconds after which claims from the user info endpoint should be refreshed */ #define OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL 0 /* default for preserving POST parameters across authentication requests */ #define OIDC_DEFAULT_PRESERVE_POST 0 /* default for passing the access token in a header/environment variable */ #define OIDC_DEFAULT_PASS_REFRESH_TOKEN 0 /* default for passing app info in headers */ #define OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS 1 /* default for passing app info in environment variables */ #define OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS 1 /* default value for the token introspection interval (0 = disabled, no expiry of claims) */ #define OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL 0 /* default action to take on an incoming unauthenticated request */ #define OIDC_DEFAULT_UNAUTH_ACTION OIDC_UNAUTH_AUTHENTICATE /* default action to take on an incoming authorized request */ #define OIDC_DEFAULT_UNAUTZ_ACTION OIDC_UNAUTZ_RETURN403 /* defines for how long provider metadata will be cached */ #define OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL 0 /* defines the default token binding policy for a provider */ #define OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY OIDC_TOKEN_BINDING_POLICY_OPTIONAL /* define the default HTTP method used to send the authentication request to the provider */ #define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET /* define whether the issuer will be added to the redirect uri by default to mitigate the IDP mixup attack */ #define OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI 0 #define OIDCProviderMetadataURL "OIDCProviderMetadataURL" #define OIDCProviderIssuer "OIDCProviderIssuer" #define OIDCProviderAuthorizationEndpoint "OIDCProviderAuthorizationEndpoint" #define OIDCProviderTokenEndpoint "OIDCProviderTokenEndpoint" #define OIDCProviderTokenEndpointAuth "OIDCProviderTokenEndpointAuth" #define OIDCProviderTokenEndpointParams "OIDCProviderTokenEndpointParams" #define OIDCProviderRegistrationEndpointJson "OIDCProviderRegistrationEndpointJson" #define OIDCProviderUserInfoEndpoint "OIDCProviderUserInfoEndpoint" #define OIDCProviderCheckSessionIFrame "OIDCProviderCheckSessionIFrame" #define OIDCProviderEndSessionEndpoint "OIDCProviderEndSessionEndpoint" #define OIDCProviderJwksUri "OIDCProviderJwksUri" #define OIDCResponseType "OIDCResponseType" #define OIDCResponseMode "OIDCResponseMode" #define OIDCPublicKeyFiles "OIDCPublicKeyFiles" #define OIDCClientJwksUri "OIDCClientJwksUri" #define OIDCIDTokenSignedResponseAlg "OIDCIDTokenSignedResponseAlg" #define OIDCIDTokenEncryptedResponseAlg "OIDCIDTokenEncryptedResponseAlg" #define OIDCIDTokenEncryptedResponseEnc "OIDCIDTokenEncryptedResponseEnc" #define OIDCUserInfoSignedResponseAlg "OIDCUserInfoSignedResponseAlg" #define OIDCUserInfoEncryptedResponseAlg "OIDCUserInfoEncryptedResponseAlg" #define OIDCUserInfoEncryptedResponseEnc "OIDCUserInfoEncryptedResponseEnc" #define OIDCUserInfoTokenMethod "OIDCUserInfoTokenMethod" #define OIDCTokenBindingPolicy "OIDCTokenBindingPolicy" #define OIDCSSLValidateServer "OIDCSSLValidateServer" #define OIDCClientName "OIDCClientName" #define OIDCClientContact "OIDCClientContact" #define OIDCScope "OIDCScope" #define OIDCPathScope "OIDCPathScope" #define OIDCJWKSRefreshInterval "OIDCJWKSRefreshInterval" #define OIDCIDTokenIatSlack "OIDCIDTokenIatSlack" #define OIDCSessionMaxDuration "OIDCSessionMaxDuration" #define OIDCAuthRequestParams "OIDCAuthRequestParams" #define OIDCPathAuthRequestParams "OIDCPathAuthRequestParams" #define OIDCPKCEMethod "OIDCPKCEMethod" #define OIDCClientID "OIDCClientID" #define OIDCClientSecret "OIDCClientSecret" #define OIDCClientTokenEndpointCert "OIDCClientTokenEndpointCert" #define OIDCClientTokenEndpointKey "OIDCClientTokenEndpointKey" #define OIDCDefaultLoggedOutURL "OIDCDefaultLoggedOutURL" #define OIDCCookieHTTPOnly "OIDCCookieHTTPOnly" #define OIDCCookieSameSite "OIDCCookieSameSite" #define OIDCOutgoingProxy "OIDCOutgoingProxy" #define OIDCCryptoPassphrase "OIDCCryptoPassphrase" #define OIDCClaimDelimiter "OIDCClaimDelimiter" #define OIDCPassIDTokenAs "OIDCPassIDTokenAs" #define OIDCPassUserInfoAs "OIDCPassUserInfoAs" #define OIDCOAuthClientID "OIDCOAuthClientID" #define OIDCOAuthClientSecret "OIDCOAuthClientSecret" #define OIDCOAuthIntrospectionClientAuthBearerToken "OIDCOAuthIntrospectionClientAuthBearerToken" #define OIDCOAuthIntrospectionEndpoint "OIDCOAuthIntrospectionEndpoint" #define OIDCOAuthIntrospectionEndpointMethod "OIDCOAuthIntrospectionEndpointMethod" #define OIDCOAuthIntrospectionEndpointParams "OIDCOAuthIntrospectionEndpointParams" #define OIDCOAuthIntrospectionEndpointAuth "OIDCOAuthIntrospectionEndpointAuth" #define OIDCOAuthIntrospectionEndpointCert "OIDCOAuthIntrospectionEndpointCert" #define OIDCOAuthIntrospectionEndpointKey "OIDCOAuthIntrospectionEndpointKey" #define OIDCOAuthIntrospectionTokenParamName "OIDCOAuthIntrospectionTokenParamName" #define OIDCOAuthTokenExpiryClaim "OIDCOAuthTokenExpiryClaim" #define OIDCOAuthSSLValidateServer "OIDCOAuthSSLValidateServer" #define OIDCOAuthVerifyCertFiles "OIDCOAuthVerifyCertFiles" #define OIDCOAuthVerifySharedKeys "OIDCOAuthVerifySharedKeys" #define OIDCOAuthVerifyJwksUri "OIDCOAuthVerifyJwksUri" #define OIDCHTTPTimeoutLong "OIDCHTTPTimeoutLong" #define OIDCHTTPTimeoutShort "OIDCHTTPTimeoutShort" #define OIDCStateTimeout "OIDCStateTimeout" #define OIDCSessionInactivityTimeout "OIDCSessionInactivityTimeout" #define OIDCMetadataDir "OIDCMetadataDir" #define OIDCSessionCacheFallbackToCookie "OIDCSessionCacheFallbackToCookie" #define OIDCSessionCookieChunkSize "OIDCSessionCookieChunkSize" #define OIDCScrubRequestHeaders "OIDCScrubRequestHeaders" #define OIDCCacheType "OIDCCacheType" #define OIDCCacheEncrypt "OIDCCacheEncrypt" #define OIDCCacheDir "OIDCCacheDir" #define OIDCCacheFileCleanInterval "OIDCCacheFileCleanInterval" #define OIDCRedisCachePassword "OIDCRedisCachePassword" #define OIDCHTMLErrorTemplate "OIDCHTMLErrorTemplate" #define OIDCDiscoverURL "OIDCDiscoverURL" #define OIDCPassCookies "OIDCPassCookies" #define OIDCStripCookies "OIDCStripCookies" #define OIDCAuthNHeader "OIDCAuthNHeader" #define OIDCCookie "OIDCCookie" #define OIDCUnAuthAction "OIDCUnAuthAction" #define OIDCUnAutzAction "OIDCUnAutzAction" #define OIDCPassClaimsAs "OIDCPassClaimsAs" #define OIDCOAuthAcceptTokenAs "OIDCOAuthAcceptTokenAs" #define OIDCUserInfoRefreshInterval "OIDCUserInfoRefreshInterval" #define OIDCOAuthTokenIntrospectionInterval "OIDCOAuthTokenIntrospectionInterval" #define OIDCPreservePost "OIDCPreservePost" #define OIDCPassRefreshToken "OIDCPassRefreshToken" #define OIDCRequestObject "OIDCRequestObject" #define OIDCProviderMetadataRefreshInterval "OIDCProviderMetadataRefreshInterval" #define OIDCProviderAuthRequestMethod "OIDCProviderAuthRequestMethod" #define OIDCBlackListedClaims "OIDCBlackListedClaims" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * directory related configuration */ typedef struct oidc_dir_cfg { char *discover_url; char *cookie_path; char *cookie; char *authn_header; int unauth_action; int unautz_action; apr_array_header_t *pass_cookies; apr_array_header_t *strip_cookies; int pass_info_in_headers; int pass_info_in_env_vars; int oauth_accept_token_in; apr_hash_t *oauth_accept_token_options; int oauth_token_introspect_interval; int preserve_post; int pass_refresh_token; char *path_auth_request_params; char *path_scope; } oidc_dir_cfg; #define OIDC_CONFIG_DIR_RV(cmd, rv) rv != NULL ? apr_psprintf(cmd->pool, "Invalid value for directive '%s': %s", cmd->directive->directive, rv) : NULL /* * set a boolean value in the server config */ static const char *oidc_set_flag_slot(cmd_parms *cmd, void *struct_ptr, int arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); return ap_set_flag_slot(cmd, cfg, arg); } /* * set a string value in the server config */ static const char *oidc_set_string_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); return ap_set_string_slot(cmd, cfg, arg); } /* * set an integer value in the server config */ static const char *oidc_set_int_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); return ap_set_int_slot(cmd, cfg, arg); } /* * set a URL value in a config record */ static const char *oidc_set_url_slot_type(cmd_parms *cmd, void *ptr, const char *arg, const char *type) { const char *rv = type != NULL ? oidc_valid_url(cmd->pool, arg, type) : oidc_valid_http_url(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, ptr, arg); return rv; } /* * set a HTTPS value in the server config */ static const char *oidc_set_https_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); return oidc_set_url_slot_type(cmd, cfg, arg, "https"); } /* * set a HTTPS/HTTP value in the server config */ static const char *oidc_set_url_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); return oidc_set_url_slot_type(cmd, cfg, arg, NULL); } /* * set a relative or absolute URL value in the server config */ static const char *oidc_set_relative_or_absolute_url_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); if (arg[0] == OIDC_CHAR_FORWARD_SLASH) { // relative uri apr_uri_t uri; if (apr_uri_parse(cmd->pool, arg, &uri) != APR_SUCCESS) { const char *rv = apr_psprintf(cmd->pool, "cannot parse '%s' as relative URI", arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } else { return ap_set_string_slot(cmd, cfg, arg); } } else { // absolute uri return oidc_set_url_slot_type(cmd, cfg, arg, NULL); } } /* * set a HTTPS/HTTP value in the directory config */ static const char *oidc_set_url_slot_dir_cfg(cmd_parms *cmd, void *ptr, const char *arg) { return oidc_set_url_slot_type(cmd, ptr, arg, NULL); } /* * set a directory value in the server config */ // TODO: it's not really a syntax error... (could be fixed at runtime but then we'd have to restart the server) static const char *oidc_set_dir_slot(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_dir(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return rv; } /* * set the cookie domain in the server config and check it syntactically */ static const char *oidc_set_cookie_domain(cmd_parms *cmd, void *ptr, const char *value) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_cookie_domain(cmd->pool, value); if (rv == NULL) cfg->cookie_domain = apr_pstrdup(cmd->pool, value); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the session storage type */ static const char *oidc_set_session_type(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_type(cmd->pool, arg, &cfg->session_type, &cfg->persistent_session_cookie); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the maximum size of a shared memory cache entry and enforces a minimum */ static const char *oidc_set_cache_shm_entry_size_max(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_cache_shm_entry_size_max(cmd->pool, arg, &cfg->cache_shm_entry_size_max); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the cache type */ static const char *oidc_set_cache_type(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_cache_type(cmd->pool, arg, &cfg->cache); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set SSL validation slot */ static const char *oidc_set_ssl_validate_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); int b = 0; const char *rv = oidc_parse_boolean(cmd->pool, arg, &b); if (rv == NULL) rv = ap_set_flag_slot(cmd, cfg, b); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * return the right token endpoint authentication method validation function, based on whether private keys are set */ oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg *cfg) { return (cfg->private_keys != NULL) ? oidc_valid_endpoint_auth_method : oidc_valid_endpoint_auth_method_no_private_key; } /* * set an authentication method for an endpoint and check it is one that we support */ static const char *oidc_set_endpoint_auth_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_cfg_get_valid_endpoint_auth_function(cfg)(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the response type used */ static const char *oidc_set_response_type(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_response_type(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type) { const char *rv = oidc_valid_pkce_method(pool, arg); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_PLAIN) == 0) { *type = &oidc_pkce_plain; } else if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_S256) == 0) { *type = &oidc_pkce_s256; } else if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_REFERRED_TB) == 0) { *type = &oidc_pkce_referred_tb; } return NULL; } /* * define the PCKE method to use */ static const char *oidc_set_pkce_method(cmd_parms *cmd, void *ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_pkce_type(cmd->pool, arg, &cfg->provider.pkce); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the response mode used */ static const char *oidc_set_response_mode(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_response_mode(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the signing algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_signed_response_alg(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_signed_response_alg(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the Content Encryption Key encryption algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_encrypted_response_alg(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_encrypted_response_alg(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the content encryption algorithm to be used by the OP (id_token/user_info) */ static const char *oidc_set_encrypted_response_enc(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_encrypted_response_enc(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the userinfo endpoint token presentation method */ static const char *oidc_set_userinfo_token_method(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_userinfo_token_method(cmd->pool, arg, &cfg->provider.userinfo_token_method); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the session inactivity timeout */ static const char *oidc_set_session_inactivity_timeout(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_inactivity_timeout(cmd->pool, arg, &cfg->session_inactivity_timeout); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the maximum session duration; 0 means take it from the ID token expiry time */ static const char *oidc_set_session_max_duration(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_session_max_duration(cmd->pool, arg, &cfg->provider.session_max_duration); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * add a public key from an X.509 file to our list of JWKs with public keys */ static const char *oidc_set_public_key_files(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); int offset = (int) (long) cmd->info; apr_hash_t **public_keys = (apr_hash_t **) ((char *) cfg + offset); char *kid = NULL, *fname = NULL; int fname_len; const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname, &fname_len, FALSE); if (rv != NULL) return rv; fname = oidc_util_get_full_path(cmd->pool, fname); if (oidc_jwk_parse_rsa_public_key(cmd->pool, kid, fname, &jwk, &err) == FALSE) { return apr_psprintf(cmd->pool, "oidc_jwk_parse_rsa_public_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(cmd->pool, err)); } if (*public_keys == NULL) *public_keys = apr_hash_make(cmd->pool); apr_hash_set(*public_keys, jwk->kid, APR_HASH_KEY_STRING, jwk); return NULL; } /* * add a shared key to a list of JWKs with shared keys */ static const char *oidc_set_shared_keys(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); int offset = (int) (long) cmd->info; apr_hash_t **shared_keys = (apr_hash_t **) ((char *) cfg + offset); char *kid = NULL, *secret = NULL; int key_len = 0; const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &secret, &key_len, TRUE); if (rv != NULL) return rv; jwk = oidc_jwk_create_symmetric_key(cmd->pool, kid, (const unsigned char *) secret, key_len, TRUE, &err); if (jwk == NULL) { return apr_psprintf(cmd->pool, "oidc_jwk_create_symmetric_key failed for (kid=%s) \"%s\": %s", kid, secret, oidc_jose_e2s(cmd->pool, err)); } if (*shared_keys == NULL) *shared_keys = apr_hash_make(cmd->pool); apr_hash_set(*shared_keys, jwk->kid, APR_HASH_KEY_STRING, jwk); return NULL; } /* * add a private key from an RSA private key file to our list of JWKs with private keys */ static const char *oidc_set_private_key_files_enc(cmd_parms *cmd, void *dummy, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; char *kid = NULL, *fname = NULL; int fname_len; const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname, &fname_len, FALSE); if (rv != NULL) return rv; fname = oidc_util_get_full_path(cmd->pool, fname); if (oidc_jwk_parse_rsa_private_key(cmd->pool, kid, fname, &jwk, &err) == FALSE) { return apr_psprintf(cmd->pool, "oidc_jwk_parse_rsa_private_key failed for (kid=%s) \"%s\": %s", kid, fname, oidc_jose_e2s(cmd->pool, err)); } if (cfg->private_keys == NULL) cfg->private_keys = apr_hash_make(cmd->pool); apr_hash_set(cfg->private_keys, jwk->kid, APR_HASH_KEY_STRING, jwk); return NULL; } /* * define how to pass the id_token/claims in HTTP headers */ static const char * oidc_set_pass_idtoken_as(cmd_parms *cmd, void *dummy, const char *v1, const char *v2, const char *v3) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_pass_idtoken_as(cmd->pool, v1, v2, v3, &cfg->pass_idtoken_as); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define how to pass the userinfo/claims in HTTP headers */ static const char * oidc_set_pass_userinfo_as(cmd_parms *cmd, void *dummy, const char *v1, const char *v2, const char *v3) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_pass_userinfo_as(cmd->pool, v1, v2, v3, &cfg->pass_userinfo_as); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define which method of pass an OAuth Bearer token is accepted */ static const char * oidc_set_accept_oauth_token_in(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; const char *rv = oidc_parse_accept_oauth_token_in(cmd->pool, arg, &dir_cfg->oauth_accept_token_in, dir_cfg->oauth_accept_token_options); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the syntax of the token expiry claim in the introspection response */ static const char * oidc_set_token_expiry_claim(cmd_parms *cmd, void *dummy, const char *claim_name, const char *claim_format, const char *claim_required) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = NULL; cfg->oauth.introspection_token_expiry_claim_name = apr_pstrdup(cmd->pool, claim_name); if ((rv == NULL) && (claim_format != NULL)) { rv = oidc_valid_claim_format(cmd->pool, claim_format); if (rv == NULL) cfg->oauth.introspection_token_expiry_claim_format = apr_pstrdup( cmd->pool, claim_format); } if ((rv == NULL) && (claim_required != NULL)) { rv = oidc_parse_claim_required(cmd->pool, claim_required, &cfg->oauth.introspection_token_expiry_claim_required); } return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * specify cookies names to pass/strip */ static const char * oidc_set_cookie_names(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; int offset = (int) (long) cmd->info; apr_array_header_t **cookie_names = (apr_array_header_t **) ((char *) dir_cfg + offset); if (*cookie_names == NULL) *cookie_names = apr_array_make(cmd->pool, 3, sizeof(const char *)); *(const char**) apr_array_push((*cookie_names)) = arg; return NULL; } /* * set the HTTP method to use in an OAuth 2.0 token introspection/validation call */ static const char * oidc_set_introspection_method(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_valid_introspection_method(cmd->pool, arg); if (rv == NULL) rv = ap_set_string_slot(cmd, cfg, arg); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set POST preservation behavior */ static const char *oidc_set_preserve_post(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; int b = 0; const char *rv = oidc_parse_boolean(cmd->pool, arg, &b); if (rv == NULL) rv = ap_set_flag_slot(cmd, dir_cfg, b); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the remote user name claims, optionally plus the regular expression applied to it */ static const char *oidc_set_remote_user_claim(cmd_parms *cmd, void *struct_ptr, const char *v1, const char *v2, const char *v3) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); int offset = (int) (long) cmd->info; oidc_remote_user_claim_t *remote_user_claim = (oidc_remote_user_claim_t *) ((char *) cfg + offset); remote_user_claim->claim_name = v1; if (v2) remote_user_claim->reg_exp = v2; if (v3) remote_user_claim->replace = v3; return NULL; } /* * define how to pass claims information to the application: in headers and/or environment variables */ static const char * oidc_set_pass_claims_as(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; const char *rv = oidc_parse_set_claims_as(cmd->pool, arg, &dir_cfg->pass_info_in_headers, &dir_cfg->pass_info_in_env_vars); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define how to act on unauthenticated requests */ static const char * oidc_set_unauth_action(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; const char *rv = oidc_parse_unauth_action(cmd->pool, arg, &dir_cfg->unauth_action); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define how to act on unauthorized requests */ static const char * oidc_set_unautz_action(cmd_parms *cmd, void *m, const char *arg) { oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m; const char *rv = oidc_parse_unautz_action(cmd->pool, arg, &dir_cfg->unautz_action); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the JWKS refresh interval */ static const char *oidc_set_jwks_refresh_interval(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_jwks_refresh_interval(cmd->pool, arg, &cfg->provider.jwks_refresh_interval); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the ID token "iat" slack */ static const char *oidc_set_idtoken_iat_slack(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_idtoken_iat_slack(cmd->pool, arg, &cfg->provider.idtoken_iat_slack); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the userinfo refresh interval */ static const char *oidc_set_userinfo_refresh_interval(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_userinfo_refresh_interval(cmd->pool, arg, &cfg->provider.userinfo_refresh_interval); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * define which data will be returned from the info hook */ static const char * oidc_set_info_hook_data(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_info_hook_data(cmd->pool, arg, &cfg->info_hook_data); return OIDC_CONFIG_DIR_RV(cmd, rv); } static const char * oidc_set_filtered_claims(cmd_parms *cmd, void *m, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); int offset = (int) (long) cmd->info; apr_hash_t **list = (apr_hash_t **) ((char *) cfg + offset); if (*list == NULL) *list = apr_hash_make(cmd->pool); apr_hash_set(*list, arg, APR_HASH_KEY_STRING, arg); return NULL; } /* * set the token binding policy */ static const char *oidc_set_token_binding_policy(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_token_binding_policy(cmd->pool, arg, &cfg->provider.token_binding_policy); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the claim prefix */ static const char *oidc_cfg_set_claim_prefix(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); if (*w == '\0' || *args != 0) cfg->claim_prefix = ""; else cfg->claim_prefix = w; return NULL; } /* * get the claim prefix */ const char *oidc_cfg_claim_prefix(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->claim_prefix == NULL) return OIDC_DEFAULT_CLAIM_PREFIX; return cfg->claim_prefix; } /* * set the HTTP method used to send the authentication request to the provider */ const char *oidc_set_auth_request_method(cmd_parms *cmd, void *struct_ptr, const char *arg) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); const char *rv = oidc_parse_auth_request_method(cmd->pool, arg, &cfg->provider.auth_request_method); return OIDC_CONFIG_DIR_RV(cmd, rv); } /* * set the introspection authorization static bearer token */ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd, void *struct_ptr, const char *args) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config( cmd->server->module_config, &auth_openidc_module); char *w = ap_getword_conf(cmd->pool, &args); cfg->oauth.introspection_client_auth_bearer_token = (*w == '\0' || *args != 0) ? "" : w; return NULL; } /* * create a new server config record with defaults */ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) { oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg)); c->merged = FALSE; c->redirect_uri = NULL; c->default_sso_url = NULL; c->default_slo_url = NULL; c->public_keys = NULL; c->private_keys = NULL; c->provider.metadata_url = NULL; c->provider.issuer = NULL; c->provider.authorization_endpoint_url = NULL; c->provider.token_endpoint_url = NULL; c->provider.token_endpoint_auth = NULL; c->provider.token_endpoint_params = NULL; c->provider.userinfo_endpoint_url = NULL; c->provider.client_id = NULL; c->provider.client_secret = NULL; c->provider.token_endpoint_tls_client_cert = NULL; c->provider.token_endpoint_tls_client_key = NULL; c->provider.registration_endpoint_url = NULL; c->provider.registration_endpoint_json = NULL; c->provider.check_session_iframe = NULL; c->provider.end_session_endpoint = NULL; c->provider.jwks_uri = NULL; c->provider.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER; c->provider.client_name = OIDC_DEFAULT_CLIENT_NAME; c->provider.client_contact = NULL; c->provider.registration_token = NULL; c->provider.scope = OIDC_DEFAULT_SCOPE; c->provider.response_type = OIDC_DEFAULT_RESPONSE_TYPE; c->provider.response_mode = NULL; c->provider.jwks_refresh_interval = OIDC_DEFAULT_JWKS_REFRESH_INTERVAL; c->provider.idtoken_iat_slack = OIDC_DEFAULT_IDTOKEN_IAT_SLACK; c->provider.session_max_duration = OIDC_DEFAULT_SESSION_MAX_DURATION; c->provider.auth_request_params = NULL; c->provider.pkce = NULL; c->provider.client_jwks_uri = NULL; c->provider.id_token_signed_response_alg = NULL; c->provider.id_token_encrypted_response_alg = NULL; c->provider.id_token_encrypted_response_enc = NULL; c->provider.userinfo_signed_response_alg = NULL; c->provider.userinfo_encrypted_response_alg = NULL; c->provider.userinfo_encrypted_response_enc = NULL; c->provider.userinfo_token_method = OIDC_USER_INFO_TOKEN_METHOD_HEADER; c->provider.auth_request_method = OIDC_DEFAULT_AUTH_REQUEST_METHOD; c->oauth.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER; c->oauth.client_id = NULL; c->oauth.client_secret = NULL; c->oauth.introspection_endpoint_tls_client_cert = NULL; c->oauth.introspection_endpoint_tls_client_key = NULL; c->oauth.introspection_endpoint_url = NULL; c->oauth.introspection_endpoint_method = OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD; c->oauth.introspection_endpoint_params = NULL; c->oauth.introspection_endpoint_auth = NULL; c->oauth.introspection_client_auth_bearer_token = NULL; c->oauth.introspection_token_param_name = OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME; c->oauth.introspection_token_expiry_claim_name = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME; c->oauth.introspection_token_expiry_claim_format = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT; c->oauth.introspection_token_expiry_claim_required = OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED; c->oauth.remote_user_claim.claim_name = OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER; c->oauth.remote_user_claim.reg_exp = NULL; c->oauth.remote_user_claim.replace = NULL; c->oauth.verify_jwks_uri = NULL; c->oauth.verify_public_keys = NULL; c->oauth.verify_shared_keys = NULL; c->cache = &oidc_cache_shm; c->cache_cfg = NULL; c->cache_encrypt = OIDC_CONFIG_POS_INT_UNSET; c->cache_file_dir = NULL; c->cache_file_clean_interval = OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL; c->cache_memcache_servers = NULL; c->cache_shm_size_max = OIDC_DEFAULT_CACHE_SHM_SIZE; c->cache_shm_entry_size_max = OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX; #ifdef USE_LIBHIREDIS c->cache_redis_server = NULL; c->cache_redis_password = NULL; #endif c->metadata_dir = NULL; c->session_type = OIDC_DEFAULT_SESSION_TYPE; c->session_cache_fallback_to_cookie = OIDC_CONFIG_POS_INT_UNSET; c->persistent_session_cookie = 0; c->session_cookie_chunk_size = OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE; c->http_timeout_long = OIDC_DEFAULT_HTTP_TIMEOUT_LONG; c->http_timeout_short = OIDC_DEFAULT_HTTP_TIMEOUT_SHORT; c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT; c->session_inactivity_timeout = OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT; c->cookie_domain = NULL; c->claim_delimiter = OIDC_DEFAULT_CLAIM_DELIMITER; c->claim_prefix = NULL; c->remote_user_claim.claim_name = OIDC_DEFAULT_CLAIM_REMOTE_USER; c->remote_user_claim.reg_exp = NULL; c->remote_user_claim.replace = NULL; c->pass_idtoken_as = OIDC_PASS_IDTOKEN_AS_CLAIMS; c->pass_userinfo_as = OIDC_PASS_USERINFO_AS_CLAIMS; c->cookie_http_only = OIDC_DEFAULT_COOKIE_HTTPONLY; c->cookie_same_site = OIDC_DEFAULT_COOKIE_SAME_SITE; c->outgoing_proxy = NULL; c->crypto_passphrase = NULL; c->scrub_request_headers = OIDC_DEFAULT_SCRUB_REQUEST_HEADERS; c->error_template = NULL; c->provider.userinfo_refresh_interval = OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL; c->provider.request_object = NULL; c->provider_metadata_refresh_interval = OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL; c->provider.token_binding_policy = OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY; c->info_hook_data = NULL; c->black_listed_claims = NULL; c->white_listed_claims = NULL; c->provider.issuer_specific_redirect_uri = OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI; return c; } /* * merge a new server config with a base one */ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) { oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg)); oidc_cfg *base = BASE; oidc_cfg *add = ADD; c->merged = TRUE; c->redirect_uri = add->redirect_uri != NULL ? add->redirect_uri : base->redirect_uri; c->default_sso_url = add->default_sso_url != NULL ? add->default_sso_url : base->default_sso_url; c->default_slo_url = add->default_slo_url != NULL ? add->default_slo_url : base->default_slo_url; c->public_keys = add->public_keys != NULL ? add->public_keys : base->public_keys; c->private_keys = add->private_keys != NULL ? add->private_keys : base->private_keys; c->provider.metadata_url = add->provider.metadata_url != NULL ? add->provider.metadata_url : base->provider.metadata_url; c->provider.issuer = add->provider.issuer != NULL ? add->provider.issuer : base->provider.issuer; c->provider.authorization_endpoint_url = add->provider.authorization_endpoint_url != NULL ? add->provider.authorization_endpoint_url : base->provider.authorization_endpoint_url; c->provider.token_endpoint_url = add->provider.token_endpoint_url != NULL ? add->provider.token_endpoint_url : base->provider.token_endpoint_url; c->provider.token_endpoint_auth = add->provider.token_endpoint_auth != NULL ? add->provider.token_endpoint_auth : base->provider.token_endpoint_auth; c->provider.token_endpoint_params = add->provider.token_endpoint_params != NULL ? add->provider.token_endpoint_params : base->provider.token_endpoint_params; c->provider.userinfo_endpoint_url = add->provider.userinfo_endpoint_url != NULL ? add->provider.userinfo_endpoint_url : base->provider.userinfo_endpoint_url; c->provider.jwks_uri = add->provider.jwks_uri != NULL ? add->provider.jwks_uri : base->provider.jwks_uri; c->provider.client_id = add->provider.client_id != NULL ? add->provider.client_id : base->provider.client_id; c->provider.client_secret = add->provider.client_secret != NULL ? add->provider.client_secret : base->provider.client_secret; c->provider.token_endpoint_tls_client_key = add->provider.token_endpoint_tls_client_key != NULL ? add->provider.token_endpoint_tls_client_key : base->provider.token_endpoint_tls_client_key; c->provider.token_endpoint_tls_client_cert = add->provider.token_endpoint_tls_client_cert != NULL ? add->provider.token_endpoint_tls_client_cert : base->provider.token_endpoint_tls_client_cert; c->provider.registration_endpoint_url = add->provider.registration_endpoint_url != NULL ? add->provider.registration_endpoint_url : base->provider.registration_endpoint_url; c->provider.registration_endpoint_json = add->provider.registration_endpoint_json != NULL ? add->provider.registration_endpoint_json : base->provider.registration_endpoint_json; c->provider.check_session_iframe = add->provider.check_session_iframe != NULL ? add->provider.check_session_iframe : base->provider.check_session_iframe; c->provider.end_session_endpoint = add->provider.end_session_endpoint != NULL ? add->provider.end_session_endpoint : base->provider.end_session_endpoint; c->provider.ssl_validate_server = add->provider.ssl_validate_server != OIDC_DEFAULT_SSL_VALIDATE_SERVER ? add->provider.ssl_validate_server : base->provider.ssl_validate_server; c->provider.client_name = apr_strnatcmp(add->provider.client_name, OIDC_DEFAULT_CLIENT_NAME) != 0 ? add->provider.client_name : base->provider.client_name; c->provider.client_contact = add->provider.client_contact != NULL ? add->provider.client_contact : base->provider.client_contact; c->provider.registration_token = add->provider.registration_token != NULL ? add->provider.registration_token : base->provider.registration_token; c->provider.scope = apr_strnatcmp(add->provider.scope, OIDC_DEFAULT_SCOPE) != 0 ? add->provider.scope : base->provider.scope; c->provider.response_type = apr_strnatcmp(add->provider.response_type, OIDC_DEFAULT_RESPONSE_TYPE) != 0 ? add->provider.response_type : base->provider.response_type; c->provider.response_mode = add->provider.response_mode != NULL ? add->provider.response_mode : base->provider.response_mode; c->provider.jwks_refresh_interval = add->provider.jwks_refresh_interval != OIDC_DEFAULT_JWKS_REFRESH_INTERVAL ? add->provider.jwks_refresh_interval : base->provider.jwks_refresh_interval; c->provider.idtoken_iat_slack = add->provider.idtoken_iat_slack != OIDC_DEFAULT_IDTOKEN_IAT_SLACK ? add->provider.idtoken_iat_slack : base->provider.idtoken_iat_slack; c->provider.session_max_duration = add->provider.session_max_duration != OIDC_DEFAULT_SESSION_MAX_DURATION ? add->provider.session_max_duration : base->provider.session_max_duration; c->provider.auth_request_params = add->provider.auth_request_params != NULL ? add->provider.auth_request_params : base->provider.auth_request_params; c->provider.pkce = add->provider.pkce != NULL ? add->provider.pkce : base->provider.pkce; c->provider.client_jwks_uri = add->provider.client_jwks_uri != NULL ? add->provider.client_jwks_uri : base->provider.client_jwks_uri; c->provider.id_token_signed_response_alg = add->provider.id_token_signed_response_alg != NULL ? add->provider.id_token_signed_response_alg : base->provider.id_token_signed_response_alg; c->provider.id_token_encrypted_response_alg = add->provider.id_token_encrypted_response_alg != NULL ? add->provider.id_token_encrypted_response_alg : base->provider.id_token_encrypted_response_alg; c->provider.id_token_encrypted_response_enc = add->provider.id_token_encrypted_response_enc != NULL ? add->provider.id_token_encrypted_response_enc : base->provider.id_token_encrypted_response_enc; c->provider.userinfo_signed_response_alg = add->provider.userinfo_signed_response_alg != NULL ? add->provider.userinfo_signed_response_alg : base->provider.userinfo_signed_response_alg; c->provider.userinfo_encrypted_response_alg = add->provider.userinfo_encrypted_response_alg != NULL ? add->provider.userinfo_encrypted_response_alg : base->provider.userinfo_encrypted_response_alg; c->provider.userinfo_encrypted_response_enc = add->provider.userinfo_encrypted_response_enc != NULL ? add->provider.userinfo_encrypted_response_enc : base->provider.userinfo_encrypted_response_enc; c->provider.userinfo_token_method = add->provider.userinfo_token_method != OIDC_USER_INFO_TOKEN_METHOD_HEADER ? add->provider.userinfo_token_method : base->provider.userinfo_token_method; c->provider.auth_request_method = add->provider.auth_request_method != OIDC_DEFAULT_AUTH_REQUEST_METHOD ? add->provider.auth_request_method : base->provider.auth_request_method; c->oauth.ssl_validate_server = add->oauth.ssl_validate_server != OIDC_DEFAULT_SSL_VALIDATE_SERVER ? add->oauth.ssl_validate_server : base->oauth.ssl_validate_server; c->oauth.client_id = add->oauth.client_id != NULL ? add->oauth.client_id : base->oauth.client_id; c->oauth.client_secret = add->oauth.client_secret != NULL ? add->oauth.client_secret : base->oauth.client_secret; c->oauth.introspection_endpoint_tls_client_key = add->oauth.introspection_endpoint_tls_client_key != NULL ? add->oauth.introspection_endpoint_tls_client_key : base->oauth.introspection_endpoint_tls_client_key; c->oauth.introspection_endpoint_tls_client_cert = add->oauth.introspection_endpoint_tls_client_cert != NULL ? add->oauth.introspection_endpoint_tls_client_cert : base->oauth.introspection_endpoint_tls_client_cert; c->oauth.introspection_endpoint_url = add->oauth.introspection_endpoint_url != NULL ? add->oauth.introspection_endpoint_url : base->oauth.introspection_endpoint_url; c->oauth.introspection_endpoint_method = apr_strnatcmp(add->oauth.introspection_endpoint_method, OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD) != 0 ? add->oauth.introspection_endpoint_method : base->oauth.introspection_endpoint_method; c->oauth.introspection_endpoint_params = add->oauth.introspection_endpoint_params != NULL ? add->oauth.introspection_endpoint_params : base->oauth.introspection_endpoint_params; c->oauth.introspection_endpoint_auth = add->oauth.introspection_endpoint_auth != NULL ? add->oauth.introspection_endpoint_auth : base->oauth.introspection_endpoint_auth; c->oauth.introspection_client_auth_bearer_token = add->oauth.introspection_client_auth_bearer_token != NULL ? add->oauth.introspection_client_auth_bearer_token : base->oauth.introspection_client_auth_bearer_token; c->oauth.introspection_token_param_name = apr_strnatcmp(add->oauth.introspection_token_param_name, OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME) != 0 ? add->oauth.introspection_token_param_name : base->oauth.introspection_token_param_name; c->oauth.introspection_token_expiry_claim_name = apr_strnatcmp(add->oauth.introspection_token_expiry_claim_name, OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME) != 0 ? add->oauth.introspection_token_expiry_claim_name : base->oauth.introspection_token_expiry_claim_name; c->oauth.introspection_token_expiry_claim_format = apr_strnatcmp(add->oauth.introspection_token_expiry_claim_format, OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT) != 0 ? add->oauth.introspection_token_expiry_claim_format : base->oauth.introspection_token_expiry_claim_format; c->oauth.introspection_token_expiry_claim_required = add->oauth.introspection_token_expiry_claim_required != OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED ? add->oauth.introspection_token_expiry_claim_required : base->oauth.introspection_token_expiry_claim_required; c->oauth.remote_user_claim.claim_name = apr_strnatcmp(add->oauth.remote_user_claim.claim_name, OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER) != 0 ? add->oauth.remote_user_claim.claim_name : base->oauth.remote_user_claim.claim_name; c->oauth.remote_user_claim.reg_exp = add->oauth.remote_user_claim.reg_exp != NULL ? add->oauth.remote_user_claim.reg_exp : base->oauth.remote_user_claim.reg_exp; c->oauth.remote_user_claim.replace = add->oauth.remote_user_claim.replace != NULL ? add->oauth.remote_user_claim.replace : base->oauth.remote_user_claim.replace; c->oauth.verify_jwks_uri = add->oauth.verify_jwks_uri != NULL ? add->oauth.verify_jwks_uri : base->oauth.verify_jwks_uri; c->oauth.verify_public_keys = add->oauth.verify_public_keys != NULL ? add->oauth.verify_public_keys : base->oauth.verify_public_keys; c->oauth.verify_shared_keys = add->oauth.verify_shared_keys != NULL ? add->oauth.verify_shared_keys : base->oauth.verify_shared_keys; c->http_timeout_long = add->http_timeout_long != OIDC_DEFAULT_HTTP_TIMEOUT_LONG ? add->http_timeout_long : base->http_timeout_long; c->http_timeout_short = add->http_timeout_short != OIDC_DEFAULT_HTTP_TIMEOUT_SHORT ? add->http_timeout_short : base->http_timeout_short; c->state_timeout = add->state_timeout != OIDC_DEFAULT_STATE_TIMEOUT ? add->state_timeout : base->state_timeout; c->session_inactivity_timeout = add->session_inactivity_timeout != OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT ? add->session_inactivity_timeout : base->session_inactivity_timeout; if (add->cache != &oidc_cache_shm) { c->cache = add->cache; } else { c->cache = base->cache; } c->cache_encrypt = add->cache_encrypt != OIDC_CONFIG_POS_INT_UNSET ? add->cache_encrypt : base->cache_encrypt; c->cache_cfg = NULL; c->cache_file_dir = add->cache_file_dir != NULL ? add->cache_file_dir : base->cache_file_dir; c->cache_file_clean_interval = add->cache_file_clean_interval != OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL ? add->cache_file_clean_interval : base->cache_file_clean_interval; c->cache_memcache_servers = add->cache_memcache_servers != NULL ? add->cache_memcache_servers : base->cache_memcache_servers; c->cache_shm_size_max = add->cache_shm_size_max != OIDC_DEFAULT_CACHE_SHM_SIZE ? add->cache_shm_size_max : base->cache_shm_size_max; c->cache_shm_entry_size_max = add->cache_shm_entry_size_max != OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX ? add->cache_shm_entry_size_max : base->cache_shm_entry_size_max; #ifdef USE_LIBHIREDIS c->cache_redis_server = add->cache_redis_server != NULL ? add->cache_redis_server : base->cache_redis_server; c->cache_redis_password = add->cache_redis_password != NULL ? add->cache_redis_password : base->cache_redis_password; #endif c->metadata_dir = add->metadata_dir != NULL ? add->metadata_dir : base->metadata_dir; c->session_type = add->session_type != OIDC_DEFAULT_SESSION_TYPE ? add->session_type : base->session_type; c->session_cache_fallback_to_cookie = add->session_cache_fallback_to_cookie != OIDC_CONFIG_POS_INT_UNSET ? add->session_cache_fallback_to_cookie : base->session_cache_fallback_to_cookie; c->persistent_session_cookie = add->persistent_session_cookie != 0 ? add->persistent_session_cookie : base->persistent_session_cookie; c->session_cookie_chunk_size = add->session_cookie_chunk_size != OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE ? add->session_cookie_chunk_size : base->session_cookie_chunk_size; c->cookie_domain = add->cookie_domain != NULL ? add->cookie_domain : base->cookie_domain; c->claim_delimiter = apr_strnatcmp(add->claim_delimiter, OIDC_DEFAULT_CLAIM_DELIMITER) != 0 ? add->claim_delimiter : base->claim_delimiter; c->claim_prefix = add->claim_prefix != NULL ? add->claim_prefix : base->claim_prefix; c->remote_user_claim.claim_name = apr_strnatcmp(add->remote_user_claim.claim_name, OIDC_DEFAULT_CLAIM_REMOTE_USER) != 0 ? add->remote_user_claim.claim_name : base->remote_user_claim.claim_name; c->remote_user_claim.reg_exp = add->remote_user_claim.reg_exp != NULL ? add->remote_user_claim.reg_exp : base->remote_user_claim.reg_exp; c->remote_user_claim.replace = add->remote_user_claim.replace != NULL ? add->remote_user_claim.replace : base->remote_user_claim.replace; c->pass_idtoken_as = add->pass_idtoken_as != OIDC_PASS_IDTOKEN_AS_CLAIMS ? add->pass_idtoken_as : base->pass_idtoken_as; c->pass_userinfo_as = add->pass_userinfo_as != OIDC_PASS_USERINFO_AS_CLAIMS ? add->pass_userinfo_as : base->pass_userinfo_as; c->cookie_http_only = add->cookie_http_only != OIDC_DEFAULT_COOKIE_HTTPONLY ? add->cookie_http_only : base->cookie_http_only; c->cookie_same_site = add->cookie_same_site != OIDC_DEFAULT_COOKIE_SAME_SITE ? add->cookie_same_site : base->cookie_same_site; c->outgoing_proxy = add->outgoing_proxy != NULL ? add->outgoing_proxy : base->outgoing_proxy; c->crypto_passphrase = add->crypto_passphrase != NULL ? add->crypto_passphrase : base->crypto_passphrase; c->scrub_request_headers = add->scrub_request_headers != OIDC_DEFAULT_SCRUB_REQUEST_HEADERS ? add->scrub_request_headers : base->scrub_request_headers; c->error_template = add->error_template != NULL ? add->error_template : base->error_template; c->provider.userinfo_refresh_interval = add->provider.userinfo_refresh_interval != OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL ? add->provider.userinfo_refresh_interval : base->provider.userinfo_refresh_interval; c->provider.request_object = add->provider.request_object != NULL ? add->provider.request_object : base->provider.request_object; c->provider_metadata_refresh_interval = add->provider_metadata_refresh_interval != OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL ? add->provider_metadata_refresh_interval : base->provider_metadata_refresh_interval; c->provider.token_binding_policy = add->provider.token_binding_policy != OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY ? add->provider.token_binding_policy : base->provider.token_binding_policy; c->info_hook_data = add->info_hook_data != NULL ? add->info_hook_data : base->info_hook_data; c->black_listed_claims = add->black_listed_claims != NULL ? add->black_listed_claims : base->black_listed_claims; c->white_listed_claims = add->white_listed_claims != NULL ? add->white_listed_claims : base->white_listed_claims; c->provider.issuer_specific_redirect_uri = add->provider.issuer_specific_redirect_uri != OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI ? add->provider.issuer_specific_redirect_uri : base->provider.issuer_specific_redirect_uri; return c; } int oidc_cfg_cache_encrypt(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->cache_encrypt == OIDC_CONFIG_POS_INT_UNSET) return cfg->cache->encrypt_by_default; return cfg->cache_encrypt; } int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); if (cfg->session_cache_fallback_to_cookie == OIDC_CONFIG_POS_INT_UNSET) return 0; return cfg->session_cache_fallback_to_cookie; } /* * create a new directory config record with defaults */ void *oidc_create_dir_config(apr_pool_t *pool, char *path) { oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg)); c->discover_url = OIDC_CONFIG_STRING_UNSET; c->cookie = OIDC_CONFIG_STRING_UNSET; c->cookie_path = OIDC_CONFIG_STRING_UNSET; c->authn_header = OIDC_CONFIG_STRING_UNSET; c->unauth_action = OIDC_CONFIG_POS_INT_UNSET; c->unautz_action = OIDC_CONFIG_POS_INT_UNSET; c->pass_cookies = NULL; c->strip_cookies = NULL; c->pass_info_in_headers = OIDC_CONFIG_POS_INT_UNSET; c->pass_info_in_env_vars = OIDC_CONFIG_POS_INT_UNSET; c->oauth_accept_token_in = OIDC_CONFIG_POS_INT_UNSET; c->oauth_accept_token_options = apr_hash_make(pool); c->oauth_token_introspect_interval = OIDC_CONFIG_POS_INT_UNSET; c->preserve_post = OIDC_CONFIG_POS_INT_UNSET; c->pass_refresh_token = OIDC_CONFIG_POS_INT_UNSET; c->path_auth_request_params = NULL; c->path_scope = NULL; return (c); } char *oidc_cfg_dir_discover_url(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->discover_url != NULL) && (apr_strnatcmp(dir_cfg->discover_url, OIDC_CONFIG_STRING_UNSET) == 0)) return NULL; return dir_cfg->discover_url; } char *oidc_cfg_dir_cookie(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->cookie == NULL) || ((dir_cfg->cookie != NULL) && (apr_strnatcmp(dir_cfg->cookie, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_COOKIE; return dir_cfg->cookie; } char *oidc_cfg_dir_cookie_path(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->cookie_path == NULL) || ((dir_cfg->cookie_path != NULL) && (apr_strnatcmp(dir_cfg->cookie_path, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_COOKIE_PATH; return dir_cfg->cookie_path; } char *oidc_cfg_dir_authn_header(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if ((dir_cfg->authn_header == NULL) || ((dir_cfg->authn_header != NULL) && (apr_strnatcmp(dir_cfg->authn_header, OIDC_CONFIG_STRING_UNSET) == 0))) return OIDC_DEFAULT_AUTHN_HEADER; return dir_cfg->authn_header; } apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_info_in_headers == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS; return dir_cfg->pass_info_in_headers; } apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_info_in_env_vars == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS; return dir_cfg->pass_info_in_env_vars; } apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->pass_refresh_token == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PASS_REFRESH_TOKEN; return dir_cfg->pass_refresh_token; } apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->oauth_accept_token_in == OIDC_CONFIG_POS_INT_UNSET) return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT; return dir_cfg->oauth_accept_token_in; } char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return apr_hash_get(dir_cfg->oauth_accept_token_options, key, APR_HASH_KEY_STRING); } int oidc_cfg_token_introspection_interval(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->oauth_token_introspect_interval == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL; return dir_cfg->oauth_token_introspect_interval; } int oidc_cfg_dir_preserve_post(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->preserve_post == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_PRESERVE_POST; return dir_cfg->preserve_post; } apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->pass_cookies; } apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->strip_cookies; } int oidc_dir_cfg_unauth_action(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_UNAUTH_ACTION; return dir_cfg->unauth_action; } int oidc_dir_cfg_unautz_action(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); if (dir_cfg->unautz_action == OIDC_CONFIG_POS_INT_UNSET) return OIDC_DEFAULT_UNAUTZ_ACTION; return dir_cfg->unautz_action; } char *oidc_dir_cfg_path_auth_request_params(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->path_auth_request_params; } char *oidc_dir_cfg_path_scope(request_rec *r) { oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config, &auth_openidc_module); return dir_cfg->path_scope; } /* * merge a new directory config with a base one */ void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD) { oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg)); oidc_dir_cfg *base = BASE; oidc_dir_cfg *add = ADD; c->discover_url = (apr_strnatcmp(add->discover_url, OIDC_CONFIG_STRING_UNSET) != 0) ? add->discover_url : base->discover_url; c->cookie = (apr_strnatcmp(add->cookie, OIDC_CONFIG_STRING_UNSET) != 0) ? add->cookie : base->cookie; c->cookie_path = (apr_strnatcmp(add->cookie_path, OIDC_CONFIG_STRING_UNSET) != 0) ? add->cookie_path : base->cookie_path; c->authn_header = (apr_strnatcmp(add->authn_header, OIDC_CONFIG_STRING_UNSET) != 0) ? add->authn_header : base->authn_header; c->unauth_action = add->unauth_action != OIDC_CONFIG_POS_INT_UNSET ? add->unauth_action : base->unauth_action; c->unautz_action = add->unautz_action != OIDC_CONFIG_POS_INT_UNSET ? add->unautz_action : base->unautz_action; c->pass_cookies = add->pass_cookies != NULL ? add->pass_cookies : base->pass_cookies; c->strip_cookies = add->strip_cookies != NULL ? add->strip_cookies : base->strip_cookies; c->pass_info_in_headers = add->pass_info_in_headers != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_in_headers : base->pass_info_in_headers; c->pass_info_in_env_vars = add->pass_info_in_env_vars != OIDC_CONFIG_POS_INT_UNSET ? add->pass_info_in_env_vars : base->pass_info_in_env_vars; c->oauth_accept_token_in = add->oauth_accept_token_in != OIDC_CONFIG_POS_INT_UNSET ? add->oauth_accept_token_in : base->oauth_accept_token_in; c->oauth_accept_token_options = apr_hash_count(add->oauth_accept_token_options) > 0 ? add->oauth_accept_token_options : base->oauth_accept_token_options; c->oauth_token_introspect_interval = add->oauth_token_introspect_interval != OIDC_CONFIG_POS_INT_UNSET ? add->oauth_token_introspect_interval : base->oauth_token_introspect_interval; c->preserve_post = add->preserve_post != OIDC_CONFIG_POS_INT_UNSET ? add->preserve_post : base->preserve_post; c->pass_refresh_token = add->pass_refresh_token != OIDC_CONFIG_POS_INT_UNSET ? add->pass_refresh_token : base->pass_refresh_token; c->path_auth_request_params = add->path_auth_request_params != NULL ? add->path_auth_request_params : base->path_auth_request_params; c->path_scope = add->path_scope != NULL ? add->path_scope : base->path_scope; return (c); } /* * report a config error */ static int oidc_check_config_error(server_rec *s, const char *config_str) { oidc_serror(s, "mandatory parameter '%s' is not set", config_str); return HTTP_INTERNAL_SERVER_ERROR; } /* * check the config required for the OpenID Connect RP role */ static int oidc_check_config_openid_openidc(server_rec *s, oidc_cfg *c) { apr_uri_t r_uri; apr_byte_t redirect_uri_is_relative; if ((c->metadata_dir == NULL) && (c->provider.issuer == NULL) && (c->provider.metadata_url == NULL)) { oidc_serror(s, "one of '" OIDCProviderIssuer "', '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } if (c->redirect_uri == NULL) return oidc_check_config_error(s, OIDCRedirectURI); redirect_uri_is_relative = (c->redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH); if (c->crypto_passphrase == NULL) return oidc_check_config_error(s, OIDCCryptoPassphrase); if (c->metadata_dir == NULL) { if (c->provider.metadata_url == NULL) { if (c->provider.issuer == NULL) return oidc_check_config_error(s, OIDCProviderIssuer); if (c->provider.authorization_endpoint_url == NULL) return oidc_check_config_error(s, OIDCProviderAuthorizationEndpoint); } else { apr_uri_parse(s->process->pconf, c->provider.metadata_url, &r_uri); if ((r_uri.scheme == NULL) || (apr_strnatcmp(r_uri.scheme, "https") != 0)) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCProviderMetadataURL " SHOULD be \"https\" for security reasons!", r_uri.scheme); } } if (c->provider.client_id == NULL) return oidc_check_config_error(s, OIDCClientID); } else { if (c->provider.metadata_url != NULL) { oidc_serror(s, "only one of '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' should be set"); return HTTP_INTERNAL_SERVER_ERROR; } } apr_uri_parse(s->process->pconf, c->redirect_uri, &r_uri); if (!redirect_uri_is_relative) { if (apr_strnatcmp(r_uri.scheme, "https") != 0) { oidc_swarn(s, "the URL scheme (%s) of the configured " OIDCRedirectURI " SHOULD be \"https\" for security reasons (moreover: some Providers may reject non-HTTPS URLs)", r_uri.scheme); } } if (c->cookie_domain != NULL) { if (redirect_uri_is_relative) { oidc_swarn(s, "if the configured " OIDCRedirectURI " is relative, " OIDCCookieDomain " SHOULD be empty"); } else if (!oidc_util_cookie_domain_valid(r_uri.hostname, c->cookie_domain)) { oidc_serror(s, "the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the configured " OIDCRedirectURI " (%s): setting \"state\" and \"session\" cookies will not work!", c->cookie_domain, r_uri.hostname, c->redirect_uri); return HTTP_INTERNAL_SERVER_ERROR; } } return OK; } /* * check the config required for the OAuth 2.0 RS role */ static int oidc_check_config_oauth(server_rec *s, oidc_cfg *c) { if (c->oauth.introspection_endpoint_url == NULL) { if ((c->oauth.verify_jwks_uri == NULL) && (c->oauth.verify_public_keys == NULL) && (c->oauth.verify_shared_keys == NULL)) { oidc_serror(s, "one of '" OIDCOAuthIntrospectionEndpoint "', '" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "' must be set"); return HTTP_INTERNAL_SERVER_ERROR; } } else if ((c->oauth.verify_jwks_uri != NULL) || (c->oauth.verify_public_keys != NULL) || (c->oauth.verify_shared_keys != NULL)) { oidc_serror(s, "only '" OIDCOAuthIntrospectionEndpoint "' OR one (or more) out of ('" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "') must be set"); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /* * check the config of a vhost */ static int oidc_config_check_vhost_config(apr_pool_t *pool, server_rec *s) { oidc_cfg *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_sdebug(s, "enter"); if ((cfg->metadata_dir != NULL) || (cfg->provider.issuer != NULL) || (cfg->provider.metadata_url != NULL) || (cfg->redirect_uri != NULL)) { if (oidc_check_config_openid_openidc(s, cfg) != OK) return HTTP_INTERNAL_SERVER_ERROR; } if ((cfg->oauth.client_id != NULL) || (cfg->oauth.client_secret != NULL) || (cfg->oauth.introspection_endpoint_url != NULL) || (cfg->oauth.verify_jwks_uri != NULL) || (cfg->oauth.verify_public_keys != NULL) || (cfg->oauth.verify_shared_keys != NULL)) { if (oidc_check_config_oauth(s, cfg) != OK) return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /* * check the config of a merged vhost */ static int oidc_config_check_merged_vhost_configs(apr_pool_t *pool, server_rec *s) { int status = OK; while (s != NULL && status == OK) { oidc_cfg *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->merged) { status = oidc_config_check_vhost_config(pool, s); } s = s->next; } return status; } /* * check if any merged vhost configs exist */ static int oidc_config_merged_vhost_configs_exist(server_rec *s) { while (s != NULL) { oidc_cfg *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->merged) { return TRUE; } s = s->next; } return FALSE; } /* * SSL initialization magic copied from mod_auth_cas */ #if defined(OPENSSL_THREADS) && APR_HAS_THREADS static apr_thread_mutex_t **ssl_locks; static int ssl_num_locks; static void oidc_ssl_locking_callback(int mode, int type, const char *file, int line) { if (type < ssl_num_locks) { if (mode & CRYPTO_LOCK) apr_thread_mutex_lock(ssl_locks[type]); else apr_thread_mutex_unlock(ssl_locks[type]); } } #ifdef OPENSSL_NO_THREADID static unsigned long oidc_ssl_id_callback(void) { return (unsigned long) apr_os_thread_current(); } #else static void oidc_ssl_id_callback(CRYPTO_THREADID *id) { CRYPTO_THREADID_set_numeric(id, (unsigned long) apr_os_thread_current()); } #endif /* OPENSSL_NO_THREADID */ #endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */ static apr_status_t oidc_cleanup_child(void *data) { server_rec *sp = (server_rec *) data; while (sp != NULL) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->destroy != NULL) { if (cfg->cache->destroy(sp) != APR_SUCCESS) { oidc_serror(sp, "cache destroy function failed"); } } // can do this even though we haven't got a deep copy // since references within the object will be set to NULL oidc_jwk_list_destroy(sp->process->pool, cfg->oauth.verify_public_keys); oidc_jwk_list_destroy(sp->process->pool, cfg->oauth.verify_shared_keys); oidc_jwk_list_destroy(sp->process->pool, cfg->public_keys); oidc_jwk_list_destroy(sp->process->pool, cfg->private_keys); sp = sp->next; } return APR_SUCCESS; } static apr_status_t oidc_cleanup_parent(void *data) { oidc_cleanup_child(data); #if (defined (OPENSSL_THREADS) && APR_HAS_THREADS) if (CRYPTO_get_locking_callback() == oidc_ssl_locking_callback) CRYPTO_set_locking_callback(NULL); #ifdef OPENSSL_NO_THREADID if (CRYPTO_get_id_callback() == oidc_ssl_id_callback) CRYPTO_set_id_callback(NULL); #else if (CRYPTO_THREADID_get_callback() == oidc_ssl_id_callback) CRYPTO_THREADID_set_callback(NULL); #endif /* OPENSSL_NO_THREADID */ #endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */ EVP_cleanup(); curl_global_cleanup(); ap_log_error(APLOG_MARK, APLOG_INFO, 0, (server_rec * ) data, "%s - shutdown", NAMEVERSION); return APR_SUCCESS; } /* * handler that is called (twice) after the configuration phase; check if everything is OK */ static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s) { const char *userdata_key = "oidc_post_config"; void *data = NULL; int i; /* Since the post_config hook is invoked twice (once * for 'sanity checking' of the config and once for * the actual server launch, we have to use a hack * to not run twice */ apr_pool_userdata_get(&data, userdata_key, s->process->pool); if (data == NULL) { apr_pool_userdata_set((const void *) 1, userdata_key, apr_pool_cleanup_null, s->process->pool); return OK; } ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "%s - init - cjose %s, %s, EC=%s, GCM=%s, Redis=%s, JQ=%s", NAMEVERSION, cjose_version(), OPENSSL_VERSION_TEXT, OIDC_JOSE_EC_SUPPORT ? "yes" : "no", OIDC_JOSE_GCM_SUPPORT ? "yes" : "no", #ifdef USE_LIBHIREDIS "yes" #else "no" #endif , #ifdef USE_LIBJQ "yes" #else "no" #endif ); curl_global_init(CURL_GLOBAL_ALL); OpenSSL_add_all_digests(); #if (defined(OPENSSL_THREADS) && APR_HAS_THREADS) ssl_num_locks = CRYPTO_num_locks(); ssl_locks = apr_pcalloc(s->process->pool, ssl_num_locks * sizeof(*ssl_locks)); for (i = 0; i < ssl_num_locks; i++) apr_thread_mutex_create(&(ssl_locks[i]), APR_THREAD_MUTEX_DEFAULT, s->process->pool); #ifdef OPENSSL_NO_THREADID if (CRYPTO_get_locking_callback() == NULL && CRYPTO_get_id_callback() == NULL) { CRYPTO_set_locking_callback(oidc_ssl_locking_callback); CRYPTO_set_id_callback(oidc_ssl_id_callback); } #else if (CRYPTO_get_locking_callback() == NULL && CRYPTO_THREADID_get_callback() == NULL) { CRYPTO_set_locking_callback(oidc_ssl_locking_callback); CRYPTO_THREADID_set_callback(oidc_ssl_id_callback); } #endif /* OPENSSL_NO_THREADID */ #endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */ apr_pool_cleanup_register(pool, s, oidc_cleanup_parent, apr_pool_cleanup_null); server_rec *sp = s; while (sp != NULL) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->post_config != NULL) { if (cfg->cache->post_config(sp) != OK) return HTTP_INTERNAL_SERVER_ERROR; } sp = sp->next; } /* * Apache has a base vhost that true vhosts derive from. * There are two startup scenarios: * * 1. Only the base vhost contains OIDC settings. * No server configs have been merged. * Only the base vhost needs to be checked. * * 2. The base vhost contains zero or more OIDC settings. * One or more vhosts override these. * These vhosts have a merged config. * All merged configs need to be checked. */ if (!oidc_config_merged_vhost_configs_exist(s)) { /* nothing merged, only check the base vhost */ return oidc_config_check_vhost_config(pool, s); } return oidc_config_check_merged_vhost_configs(pool, s); } #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 static const authz_provider oidc_authz_claim_provider = { &oidc_authz_checker_claim, NULL, }; #ifdef USE_LIBJQ static const authz_provider oidc_authz_claims_expr_provider = { &oidc_authz_checker_claims_expr, NULL, }; #endif #endif /* * initialize cache context in child process if required */ static void oidc_child_init(apr_pool_t *p, server_rec *s) { server_rec *sp = s; while (sp != NULL) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config, &auth_openidc_module); if (cfg->cache->child_init != NULL) { if (cfg->cache->child_init(p, sp) != APR_SUCCESS) { oidc_serror(sp, "cfg->cache->child_init failed"); } } sp = sp->next; } apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null); } /* * fixup handler: be authoritative for environment variables at late processing */ static int oidc_auth_fixups(request_rec *r) { apr_table_t *env = NULL; apr_pool_userdata_get((void **) &env, OIDC_USERDATA_ENV_KEY, r->pool); if ((env == NULL) || apr_is_empty_table(env)) return DECLINED; oidc_debug(r, "overlaying env with %d elements", apr_table_elts(env)->nelts); r->subprocess_env = apr_table_overlay(r->pool, r->subprocess_env, env); return OK; } /* * register our authentication and authorization functions */ void oidc_register_hooks(apr_pool_t *pool) { ap_hook_post_config(oidc_post_config, NULL, NULL, APR_HOOK_LAST); ap_hook_child_init(oidc_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(oidc_content_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_fixups(oidc_auth_fixups, NULL, NULL, APR_HOOK_MIDDLE); #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 ap_hook_check_authn(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider, AP_AUTH_INTERNAL_PER_CONF); #ifdef USE_LIBJQ ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP, OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0", &oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF); #endif #else static const char * const authzSucc[] = {"mod_authz_user.c", NULL}; ap_hook_check_user_id(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_auth_checker(oidc_auth_checker, NULL, authzSucc, APR_HOOK_MIDDLE); #endif } /* * set of configuration primitives */ const command_rec oidc_config_cmds[] = { AP_INIT_TAKE1(OIDCProviderMetadataURL, oidc_set_url_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.metadata_url), RSRC_CONF, "OpenID Connect OP configuration metadata URL."), AP_INIT_TAKE1(OIDCProviderIssuer, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.issuer), RSRC_CONF, "OpenID Connect OP issuer identifier."), AP_INIT_TAKE1(OIDCProviderAuthorizationEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.authorization_endpoint_url), RSRC_CONF, "Define the OpenID OP Authorization Endpoint URL (e.g.: https://localhost:9031/as/authorization.oauth2)"), AP_INIT_TAKE1(OIDCProviderTokenEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_url), RSRC_CONF, "Define the OpenID OP Token Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), AP_INIT_TAKE1(OIDCProviderTokenEndpointAuth, oidc_set_endpoint_auth_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_auth), RSRC_CONF, "Specify an authentication method for the OpenID OP Token Endpoint (e.g.: client_secret_basic)"), AP_INIT_TAKE1(OIDCProviderTokenEndpointParams, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_params), RSRC_CONF, "Define extra parameters that will be posted to the OpenID OP Token Endpoint (e.g.: param1=value1¶m2=value2, all urlencoded)."), AP_INIT_TAKE1(OIDCProviderRegistrationEndpointJson, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.registration_endpoint_json), RSRC_CONF, "Define a JSON object with parameters that will be merged into the client registration request to the OpenID OP Registration Endpoint (e.g.: { \"request_uris\" : [ \"https://example.com/uri\"] })."), AP_INIT_TAKE1(OIDCProviderUserInfoEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_endpoint_url), RSRC_CONF, "Define the OpenID OP UserInfo Endpoint URL (e.g.: https://localhost:9031/idp/userinfo.openid)"), AP_INIT_TAKE1(OIDCProviderCheckSessionIFrame, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.check_session_iframe), RSRC_CONF, "Define the OpenID OP Check Session iFrame URL."), AP_INIT_TAKE1(OIDCProviderEndSessionEndpoint, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.end_session_endpoint), RSRC_CONF, "Define the OpenID OP End Session Endpoint URL."), AP_INIT_TAKE1(OIDCProviderJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.jwks_uri), RSRC_CONF, "Define the OpenID OP JWKS URL (e.g.: https://localhost:9031/pf/JWKS)"), AP_INIT_TAKE1(OIDCResponseType, oidc_set_response_type, (void *)APR_OFFSETOF(oidc_cfg, provider.response_type), RSRC_CONF, "The response type (or OpenID Connect Flow) used; must be one of \"code\", \"id_token\", \"id_token token\", \"code id_token\", \"code token\" or \"code id_token token\" (serves as default value for discovered OPs too)"), AP_INIT_TAKE1(OIDCResponseMode, oidc_set_response_mode, (void *)APR_OFFSETOF(oidc_cfg, provider.response_mode), RSRC_CONF, "The response mode used; must be one of \"fragment\", \"query\" or \"form_post\" (serves as default value for discovered OPs too)"), AP_INIT_ITERATE(OIDCPublicKeyFiles, oidc_set_public_key_files, (void *)APR_OFFSETOF(oidc_cfg, public_keys), RSRC_CONF, "The fully qualified names of the files that contain the X.509 certificates that contains the RSA public keys that can be used for encryption by the OP."), AP_INIT_ITERATE(OIDCPrivateKeyFiles, oidc_set_private_key_files_enc, NULL, RSRC_CONF, "The fully qualified names of the files that contain the RSA private keys that can be used to decrypt content sent to us by the OP."), AP_INIT_TAKE1(OIDCClientJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.client_jwks_uri), RSRC_CONF, "Define the Client JWKS URL (e.g.: https://localhost/protected/?jwks=rsa)"), AP_INIT_TAKE1(OIDCIDTokenSignedResponseAlg, oidc_set_signed_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_signed_response_alg), RSRC_CONF, "The algorithm that the OP should use to sign the id_token (used only in dynamic client registration); must be one of [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512]"), AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseAlg, oidc_set_encrypted_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_alg), RSRC_CONF, "The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"), AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseEnc, oidc_set_encrypted_response_enc, (void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_enc), RSRC_CONF, "The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"), AP_INIT_TAKE1(OIDCUserInfoSignedResponseAlg, oidc_set_signed_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_signed_response_alg), RSRC_CONF, "The algorithm that the OP should use to sign the UserInfo response (used only in dynamic client registration); must be one of [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512]"), AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseAlg, oidc_set_encrypted_response_alg, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_alg), RSRC_CONF, "The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"), AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseEnc, oidc_set_encrypted_response_enc, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_enc), RSRC_CONF, "The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"), AP_INIT_TAKE1(OIDCUserInfoTokenMethod, oidc_set_userinfo_token_method, (void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_token_method), RSRC_CONF, "The method that is used to present the access token to the userinfo endpoint; must be one of [authz_header|post_param]"), AP_INIT_TAKE1(OIDCTokenBindingPolicy, oidc_set_token_binding_policy, (void *)APR_OFFSETOF(oidc_cfg, provider.token_binding_policy), RSRC_CONF, "The token binding policy used with the provider; must be one of [disabled|optional|required|enforced]"), AP_INIT_TAKE1(OIDCSSLValidateServer, oidc_set_ssl_validate_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.ssl_validate_server), RSRC_CONF, "Require validation of the OpenID Connect OP SSL server certificate for successful authentication (On or Off)"), AP_INIT_TAKE1(OIDCClientName, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.client_name), RSRC_CONF, "Define the (client_name) name that the client uses for dynamic registration to the OP."), AP_INIT_TAKE1(OIDCClientContact, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.client_contact), RSRC_CONF, "Define the contact that the client registers in dynamic registration with the OP."), AP_INIT_TAKE1(OIDCScope, oidc_set_string_slot, (void *) APR_OFFSETOF(oidc_cfg, provider.scope), RSRC_CONF, "Define the OpenID Connect scope that is requested from the OP."), AP_INIT_TAKE1(OIDCPathScope, ap_set_string_slot, (void*)APR_OFFSETOF(oidc_dir_cfg, path_scope), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the OpenID Connect scope that is requested from all providers for a specific path/context."), AP_INIT_TAKE1(OIDCJWKSRefreshInterval, oidc_set_jwks_refresh_interval, (void*)APR_OFFSETOF(oidc_cfg, provider.jwks_refresh_interval), RSRC_CONF, "Duration in seconds after which retrieved JWS should be refreshed."), AP_INIT_TAKE1(OIDCIDTokenIatSlack, oidc_set_idtoken_iat_slack, (void*)APR_OFFSETOF(oidc_cfg, provider.idtoken_iat_slack), RSRC_CONF, "Acceptable offset (both before and after) for checking the \"iat\" (= issued at) timestamp in the id_token."), AP_INIT_TAKE1(OIDCSessionMaxDuration, oidc_set_session_max_duration, (void*)APR_OFFSETOF(oidc_cfg, provider.session_max_duration), RSRC_CONF, "Maximum duration of a session in seconds."), AP_INIT_TAKE1(OIDCAuthRequestParams, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_params), RSRC_CONF, "Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."), AP_INIT_TAKE1(OIDCPathAuthRequestParams, ap_set_string_slot, (void*)APR_OFFSETOF(oidc_dir_cfg, path_auth_request_params), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."), AP_INIT_TAKE1(OIDCPKCEMethod, oidc_set_pkce_method, (void *)APR_OFFSETOF(oidc_cfg, provider.pkce), RSRC_CONF, "The RFC 7636 PCKE mode used; must be one of \"plain\", \"S256\" or \"referred_tb\""), AP_INIT_TAKE1(OIDCClientID, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.client_id), RSRC_CONF, "Client identifier used in calls to OpenID Connect OP."), AP_INIT_TAKE1(OIDCClientSecret, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.client_secret), RSRC_CONF, "Client secret used in calls to OpenID Connect OP."), AP_INIT_TAKE1(OIDCClientTokenEndpointCert, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_cert), RSRC_CONF, "TLS client certificate used for calls to OpenID Connect OP token endpoint."), AP_INIT_TAKE1(OIDCClientTokenEndpointKey, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_key), RSRC_CONF, "TLS client certificate private key used for calls to OpenID Connect OP token endpoint."), AP_INIT_TAKE1(OIDCRedirectURI, oidc_set_relative_or_absolute_url_slot, (void *)APR_OFFSETOF(oidc_cfg, redirect_uri), RSRC_CONF, "Define the Redirect URI (e.g.: https://localhost:9031/protected/example/)"), AP_INIT_TAKE1(OIDCDefaultURL, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, default_sso_url), RSRC_CONF, "Defines the default URL where the user is directed to in case of 3rd-party initiated SSO."), AP_INIT_TAKE1(OIDCDefaultLoggedOutURL, oidc_set_url_slot, (void *)APR_OFFSETOF(oidc_cfg, default_slo_url), RSRC_CONF, "Defines the default URL where the user is directed to after logout."), AP_INIT_TAKE1(OIDCCookieDomain, oidc_set_cookie_domain, NULL, RSRC_CONF, "Specify domain element for OIDC session cookie."), AP_INIT_FLAG(OIDCCookieHTTPOnly, oidc_set_flag_slot, (void *) APR_OFFSETOF(oidc_cfg, cookie_http_only), RSRC_CONF, "Defines whether or not the cookie httponly flag is set on cookies."), AP_INIT_FLAG(OIDCCookieSameSite, oidc_set_flag_slot, (void *) APR_OFFSETOF(oidc_cfg, cookie_same_site), RSRC_CONF, "Defines whether or not the cookie Same-Site flag is set on cookies."), AP_INIT_TAKE1(OIDCOutgoingProxy, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, outgoing_proxy), RSRC_CONF, "Specify an outgoing proxy for your network ([:]."), AP_INIT_TAKE1(OIDCCryptoPassphrase, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, crypto_passphrase), RSRC_CONF, "Passphrase used for AES crypto on cookies and state."), AP_INIT_TAKE1(OIDCClaimDelimiter, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, claim_delimiter), RSRC_CONF, "The delimiter to use when setting multi-valued claims in the HTTP headers."), AP_INIT_RAW_ARGS(OIDCClaimPrefix, oidc_cfg_set_claim_prefix, (void*)APR_OFFSETOF(oidc_cfg, claim_prefix), RSRC_CONF, "The prefix to use when setting claims in the HTTP headers."), AP_INIT_TAKE123(OIDCRemoteUserClaim, oidc_set_remote_user_claim, (void*)APR_OFFSETOF(oidc_cfg, remote_user_claim), RSRC_CONF, "The claim that is used when setting the REMOTE_USER variable for OpenID Connect protected paths."), AP_INIT_TAKE123(OIDCPassIDTokenAs, oidc_set_pass_idtoken_as, NULL, RSRC_CONF, "The format in which the id_token is passed in (a) header(s); must be one or more of: claims|payload|serialized"), AP_INIT_TAKE123(OIDCPassUserInfoAs, oidc_set_pass_userinfo_as, NULL, RSRC_CONF, "The format in which the userinfo is passed in (a) header(s); must be one or more of: claims|json|jwt"), AP_INIT_TAKE1(OIDCOAuthClientID, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.client_id), RSRC_CONF, "Client identifier used in calls to OAuth 2.0 Authorization server validation calls."), AP_INIT_TAKE1(OIDCOAuthClientSecret, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.client_secret), RSRC_CONF, "Client secret used in calls to OAuth 2.0 Authorization server validation calls."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpoint, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_url), RSRC_CONF, "Define the OAuth AS Introspection Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointMethod, oidc_set_introspection_method, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_method), RSRC_CONF, "Define the HTTP method to use for the introspection call: one of \"GET\" or \"POST\" (default)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointParams, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_params), RSRC_CONF, "Extra parameters that need to be sent in the token introspection request (must be query-encoded like \"grant_type=urn%3Apingidentity.com%3Aoauth2%3Agrant_type%3Avalidate_bearer\"."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointAuth, oidc_set_endpoint_auth_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_auth), RSRC_CONF, "Specify an authentication method for the OAuth AS Introspection Endpoint (e.g.: client_secret_basic)"), AP_INIT_RAW_ARGS(OIDCOAuthIntrospectionClientAuthBearerToken, oidc_set_client_auth_bearer_token, NULL, RSRC_CONF, "Specify a bearer token to authorize against the OAuth AS Introspection Endpoint (e.g.: 55554ee-2491-11e3-be72-001fe2e44345 or empty to use the introspected token itself)"), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointCert, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_cert), RSRC_CONF, "TLS client certificate used for calls to the OAuth 2.0 Authorization server introspection endpoint."), AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointKey, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_key), RSRC_CONF, "TLS client certificate private key used for calls to the OAuth 2.0 Authorization server introspection endpoint."), AP_INIT_TAKE1(OIDCOAuthIntrospectionTokenParamName, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_token_param_name), RSRC_CONF, "Name of the parameter whose value carries the access token value in an validation request to the token introspection endpoint."), AP_INIT_TAKE123(OIDCOAuthTokenExpiryClaim, oidc_set_token_expiry_claim, NULL, RSRC_CONF, "Name of the claim that carries the token expiry value in the introspection result, optionally followed by absolute|relative, optionally followed by optional|mandatory"), AP_INIT_TAKE1(OIDCOAuthSSLValidateServer, oidc_set_ssl_validate_slot, (void*)APR_OFFSETOF(oidc_cfg, oauth.ssl_validate_server), RSRC_CONF, "Require validation of the OAuth 2.0 AS Validation Endpoint SSL server certificate for successful authentication (On or Off)"), AP_INIT_TAKE123(OIDCOAuthRemoteUserClaim, oidc_set_remote_user_claim, (void*)APR_OFFSETOF(oidc_cfg, oauth.remote_user_claim), RSRC_CONF, "The claim that is used when setting the REMOTE_USER variable for OAuth 2.0 protected paths."), AP_INIT_ITERATE(OIDCOAuthVerifyCertFiles, oidc_set_public_key_files, (void*)APR_OFFSETOF(oidc_cfg, oauth.verify_public_keys), RSRC_CONF, "The fully qualified names of the files that contain the X.509 certificates that contains the RSA public keys that can be used for access token validation."), AP_INIT_ITERATE(OIDCOAuthVerifySharedKeys, oidc_set_shared_keys, (void*)APR_OFFSETOF(oidc_cfg, oauth.verify_shared_keys), RSRC_CONF, "Shared secret(s) that is/are used to verify signed JWT access tokens locally."), AP_INIT_TAKE1(OIDCOAuthVerifyJwksUri, oidc_set_https_slot, (void *)APR_OFFSETOF(oidc_cfg, oauth.verify_jwks_uri), RSRC_CONF, "The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens."), AP_INIT_TAKE1(OIDCHTTPTimeoutLong, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, http_timeout_long), RSRC_CONF, "Timeout for long duration HTTP calls (default)."), AP_INIT_TAKE1(OIDCHTTPTimeoutShort, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, http_timeout_short), RSRC_CONF, "Timeout for short duration HTTP calls (registry/discovery)."), AP_INIT_TAKE1(OIDCStateTimeout, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, state_timeout), RSRC_CONF, "Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."), AP_INIT_TAKE1(OIDCSessionInactivityTimeout, oidc_set_session_inactivity_timeout, (void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout), RSRC_CONF, "Inactivity interval after which the session is invalidated when no interaction has occurred."), AP_INIT_TAKE1(OIDCMetadataDir, oidc_set_dir_slot, (void*)APR_OFFSETOF(oidc_cfg, metadata_dir), RSRC_CONF, "Directory that contains provider and client metadata files."), AP_INIT_TAKE1(OIDCSessionType, oidc_set_session_type, (void*)APR_OFFSETOF(oidc_cfg, session_type), RSRC_CONF, "OpenID Connect session storage type (Apache 2.0/2.2 only). Must be one of \"server-cache\" or \"client-cookie\" with an optional suffix \":persistent\"."), AP_INIT_FLAG(OIDCSessionCacheFallbackToCookie, oidc_set_flag_slot, (void*)APR_OFFSETOF(oidc_cfg, session_cache_fallback_to_cookie), RSRC_CONF, "Fallback to client-side cookie session storage when server side cache fails."), AP_INIT_TAKE1(OIDCSessionCookieChunkSize, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, session_cookie_chunk_size), RSRC_CONF, "Chunk size for client-cookie session storage type in bytes. Defaults to 4k. Set 0 to suppress chunking."), AP_INIT_FLAG(OIDCScrubRequestHeaders, oidc_set_flag_slot, (void *) APR_OFFSETOF(oidc_cfg, scrub_request_headers), RSRC_CONF, "Scrub user name and claim headers from the user's request."), AP_INIT_TAKE1(OIDCCacheType, oidc_set_cache_type, (void*)APR_OFFSETOF(oidc_cfg, cache), RSRC_CONF, "Cache type; must be one of \"file\", \"memcache\" or \"shm\"."), AP_INIT_FLAG(OIDCCacheEncrypt, oidc_set_flag_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_encrypt), RSRC_CONF, "Encrypt the data in the cache backend (On or Off)"), AP_INIT_TAKE1(OIDCCacheDir, oidc_set_dir_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_file_dir), RSRC_CONF, "Directory used for file-based caching."), AP_INIT_TAKE1(OIDCCacheFileCleanInterval, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_file_clean_interval), RSRC_CONF, "Cache file clean interval in seconds."), AP_INIT_TAKE1(OIDCMemCacheServers, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_memcache_servers), RSRC_CONF, "Memcache servers used for caching (space separated list of [:] tuples)"), AP_INIT_TAKE1(OIDCCacheShmMax, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_shm_size_max), RSRC_CONF, "Maximum number of cache entries to use for \"shm\" caching."), AP_INIT_TAKE1(OIDCCacheShmEntrySizeMax, oidc_set_cache_shm_entry_size_max, (void*)APR_OFFSETOF(oidc_cfg, cache_shm_entry_size_max), RSRC_CONF, "Maximum size of a single cache entry used for \"shm\" caching."), #ifdef USE_LIBHIREDIS AP_INIT_TAKE1(OIDCRedisCacheServer, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_server), RSRC_CONF, "Redis server used for caching ([:])"), AP_INIT_TAKE1(OIDCRedisCachePassword, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, cache_redis_password), RSRC_CONF, "Password for authentication to the Redis servers."), #endif AP_INIT_TAKE1(OIDCHTMLErrorTemplate, oidc_set_string_slot, (void*)APR_OFFSETOF(oidc_cfg, error_template), RSRC_CONF, "Name of a HTML error template: needs to contain two \"%s\" characters, one for the error message, one for the description."), AP_INIT_TAKE1(OIDCDiscoverURL, oidc_set_url_slot_dir_cfg, (void *)APR_OFFSETOF(oidc_dir_cfg, discover_url), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Defines an external IDP Discovery page"), AP_INIT_ITERATE(OIDCPassCookies, oidc_set_cookie_names, (void *) APR_OFFSETOF(oidc_dir_cfg, pass_cookies), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify cookies that need to be passed from the browser on to the backend to the OP/AS."), AP_INIT_ITERATE(OIDCStripCookies, oidc_set_cookie_names, (void *) APR_OFFSETOF(oidc_dir_cfg, strip_cookies), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify cookies that should be stripped from the incoming request before passing it on to the backend."), AP_INIT_TAKE1(OIDCAuthNHeader, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, authn_header), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify the HTTP header variable to set with the name of the authenticated user. By default no explicit header is added but Apache's default REMOTE_USER will be set."), AP_INIT_TAKE1(OIDCCookiePath, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, cookie_path), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the cookie path for the session cookie."), AP_INIT_TAKE1(OIDCCookie, ap_set_string_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, cookie), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Define the cookie name for the session cookie."), AP_INIT_TAKE1(OIDCUnAuthAction, oidc_set_unauth_action, (void *) APR_OFFSETOF(oidc_dir_cfg, unauth_action), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the action taken when an unauthenticated request occurs: must be one of \"auth\" (default), \"pass\" , \"401\" or \"410\"."), AP_INIT_TAKE1(OIDCUnAutzAction, oidc_set_unautz_action, (void *) APR_OFFSETOF(oidc_dir_cfg, unautz_action), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the action taken when an unauthorized request occurs: must be one of \"401\" (default), \"403\" or \"auth\"."), AP_INIT_TAKE1(OIDCPassClaimsAs, oidc_set_pass_claims_as, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify how claims are passed to the application(s); must be one of \"none\", \"headers\", \"environment\" or \"both\" (default)."), AP_INIT_ITERATE(OIDCOAuthAcceptTokenAs, oidc_set_accept_oauth_token_in, NULL, RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "The method in which an OAuth token can be presented; must be one or more of: header|post|query|cookie"), AP_INIT_TAKE1(OIDCUserInfoRefreshInterval, oidc_set_userinfo_refresh_interval, (void*)APR_OFFSETOF(oidc_cfg, provider.userinfo_refresh_interval), RSRC_CONF, "Duration in seconds after which retrieved claims from the userinfo endpoint should be refreshed."), AP_INIT_TAKE1(OIDCOAuthTokenIntrospectionInterval, ap_set_int_slot, (void *) APR_OFFSETOF(oidc_dir_cfg, oauth_token_introspect_interval), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Sets the token introspection refresh interval."), AP_INIT_TAKE1(OIDCPreservePost, oidc_set_preserve_post, (void *) APR_OFFSETOF(oidc_dir_cfg, preserve_post), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Indicates whether POST parameters will be preserved across authentication requests."), AP_INIT_FLAG(OIDCPassRefreshToken, ap_set_flag_slot, (void*)APR_OFFSETOF(oidc_dir_cfg, pass_refresh_token), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Pass the refresh token in a header and/or environment variable (On or Off)"), AP_INIT_TAKE1(OIDCRequestObject, oidc_set_string_slot, (void *)APR_OFFSETOF(oidc_cfg, provider.request_object), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "The default request object settings"), AP_INIT_TAKE1(OIDCProviderMetadataRefreshInterval, oidc_set_int_slot, (void*)APR_OFFSETOF(oidc_cfg, provider_metadata_refresh_interval), RSRC_CONF, "Provider metadata refresh interval in seconds."), AP_INIT_TAKE1(OIDCProviderAuthRequestMethod, oidc_set_auth_request_method, (void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_method), RSRC_CONF, "HTTP method used to send the authentication request to the provider (GET or POST)."), AP_INIT_ITERATE(OIDCInfoHook, oidc_set_info_hook_data, (void *)APR_OFFSETOF(oidc_cfg, info_hook_data), RSRC_CONF, "The data that will be returned from the info hook."), AP_INIT_ITERATE(OIDCBlackListedClaims, oidc_set_filtered_claims, (void *) APR_OFFSETOF(oidc_cfg, black_listed_claims), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify claims that should be removed from the userinfo and/or id_token before storing them in the session."), AP_INIT_ITERATE(OIDCWhiteListedClaims, oidc_set_filtered_claims, (void *) APR_OFFSETOF(oidc_cfg, white_listed_claims), RSRC_CONF|ACCESS_CONF|OR_AUTHCFG, "Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded)."), { NULL } }; mod_auth_openidc-2.3.3/src/util.c0000644000076500000240000020402013202556251016565 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include #include #include "http_protocol.h" #include #include "mod_auth_openidc.h" #include #include "pcre_subst.h" /* hrm, should we get rid of this by adding parameters to the (3) functions? */ extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* * base64url encode a string */ int oidc_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding) { if ((src == NULL) || (src_len <= 0)) { oidc_error(r, "not encoding anything; src=NULL and/or src_len<1"); return -1; } unsigned int enc_len = apr_base64_encode_len(src_len); char *enc = apr_palloc(r->pool, enc_len); apr_base64_encode(enc, (const char *) src, src_len); unsigned int i = 0; while (enc[i] != '\0') { if (enc[i] == '+') enc[i] = '-'; if (enc[i] == '/') enc[i] = '_'; if (enc[i] == '=') enc[i] = ','; i++; } if (remove_padding) { /* remove /0 and padding */ if (enc_len > 0) enc_len--; if ((enc_len > 0) && (enc[enc_len - 1] == ',')) enc_len--; if ((enc_len > 0) &&(enc[enc_len - 1] == ',')) enc_len--; enc[enc_len] = '\0'; } *dst = enc; return enc_len; } /* * base64url decode a string */ int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src) { if (src == NULL) { return -1; } char *dec = apr_pstrdup(pool, src); int i = 0; while (dec[i] != '\0') { if (dec[i] == '-') dec[i] = '+'; if (dec[i] == '_') dec[i] = '/'; if (dec[i] == ',') dec[i] = '='; i++; } switch (strlen(dec) % 4) { case 0: break; case 2: dec = apr_pstrcat(pool, dec, "==", NULL); break; case 3: dec = apr_pstrcat(pool, dec, "=", NULL); break; default: return 0; } int dlen = apr_base64_decode_len(dec); *dst = apr_palloc(pool, dlen); return apr_base64_decode(*dst, dec); } apr_byte_t oidc_util_jwt_create(request_rec *r, const char *secret, json_t *payload, char **compact_encoded_jwt) { apr_byte_t rv = FALSE; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwt = NULL; oidc_jwt_t *jwe = NULL; if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256, FALSE, &jwk) == FALSE) goto end; jwt = oidc_jwt_new(r->pool, TRUE, FALSE); if (jwt == NULL) { oidc_error(r, "creating JWT failed"); goto end; } jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_HS256); jwt->payload.value.json = payload; if (oidc_jwt_sign(r->pool, jwt, jwk, &err) == FALSE) { oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } jwe = oidc_jwt_new(r->pool, TRUE, FALSE); if (jwe == NULL) { oidc_error(r, "creating JWE failed"); goto end; } jwe->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_DIR); jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A256GCM); const char *cser = oidc_jwt_serialize(r->pool, jwt, &err); if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, compact_encoded_jwt, &err) == FALSE) { oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } rv = TRUE; end: if (jwe != NULL) oidc_jwt_destroy(jwe); if (jwk != NULL) oidc_jwk_destroy(jwk); if (jwt != NULL) { jwt->payload.value.json = NULL; oidc_jwt_destroy(jwt); } return rv; } apr_byte_t oidc_util_jwt_verify(request_rec *r, const char *secret, const char *compact_encoded_jwt, json_t **result) { oidc_debug(r, "enter: JWT header=%s", oidc_proto_peek_jwt_header(r, compact_encoded_jwt, NULL)); apr_byte_t rv = FALSE; oidc_jose_error_t err; oidc_jwk_t *jwk = NULL; oidc_jwt_t *jwt = NULL; if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256, FALSE, &jwk) == FALSE) goto end; apr_hash_t *keys = apr_hash_make(r->pool); apr_hash_set(keys, "", APR_HASH_KEY_STRING, jwk); if (oidc_jwt_parse(r->pool, compact_encoded_jwt, &jwt, keys, &err) == FALSE) { oidc_error(r, "parsing JWT failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } if (oidc_jwt_verify(r->pool, jwt, keys, &err) == FALSE) { oidc_error(r, "verifying JWT failed: %s", oidc_jose_e2s(r->pool, err)); goto end; } *result = json_deep_copy(jwt->payload.value.json); rv = TRUE; end: if (jwk != NULL) oidc_jwk_destroy(jwk); if (jwt != NULL) oidc_jwt_destroy(jwt); return rv; } /* * convert a character to an ENVIRONMENT-variable-safe variant */ int oidc_char_to_env(int c) { return apr_isalnum(c) ? apr_toupper(c) : '_'; } /* * compare two strings based on how they would be converted to an * environment variable, as per oidc_char_to_env. If len is specified * as less than zero, then the full strings will be compared. Returns * less than, equal to, or greater than zero based on whether the * first argument's conversion to an environment variable is less * than, equal to, or greater than the second. */ int oidc_strnenvcmp(const char *a, const char *b, int len) { int d, i = 0; while (1) { /* If len < 0 then we don't stop based on length */ if (len >= 0 && i >= len) return 0; /* If we're at the end of both strings, they're equal */ if (!*a && !*b) return 0; /* If the second string is shorter, pick it: */ if (*a && !*b) return 1; /* If the first string is shorter, pick it: */ if (!*a && *b) return -1; /* Normalize the characters as for conversion to an * environment variable. */ d = oidc_char_to_env(*a) - oidc_char_to_env(*b); if (d) return d; a++; b++; i++; } return 0; } /* * escape a string */ char *oidc_util_escape_string(const request_rec *r, const char *str) { CURL *curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); return NULL; } char *result = curl_easy_escape(curl, str, 0); if (result == NULL) { oidc_error(r, "curl_easy_escape() error"); return NULL; } char *rv = apr_pstrdup(r->pool, result); curl_free(result); curl_easy_cleanup(curl); return rv; } /* * escape a string */ char *oidc_util_unescape_string(const request_rec *r, const char *str) { CURL *curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); return NULL; } char *result = curl_easy_unescape(curl, str, 0, 0); if (result == NULL) { oidc_error(r, "curl_easy_unescape() error"); return NULL; } char *rv = apr_pstrdup(r->pool, result); curl_free(result); curl_easy_cleanup(curl); //oidc_debug(r, "input=\"%s\", output=\"%s\"", str, rv); return rv; } /* * HTML escape a string */ char *oidc_util_html_escape(apr_pool_t *pool, const char *s) { // TODO: this has performance/memory issues for large chunks of HTML const char chars[6] = { '&', '\'', '\"', '>', '<', '\0' }; const char * const replace[] = { "&", "'", """, ">", "<", }; unsigned int i, j = 0, k, n = 0, len = strlen(chars); unsigned int m = 0; char *r = apr_pcalloc(pool, strlen(s) * 6); for (i = 0; i < strlen(s); i++) { for (n = 0; n < len; n++) { if (s[i] == chars[n]) { m = (unsigned int)strlen(replace[n]); for (k = 0; k < m; k++) r[j + k] = replace[n][k]; j += m; break; } } if (n == len) { r[j] = s[i]; j++; } } r[j] = '\0'; return apr_pstrdup(pool, r); } /* * get the URL scheme that is currently being accessed */ static const char *oidc_get_current_url_scheme(const request_rec *r) { /* first see if there's a proxy/load-balancer in front of us */ const char *scheme_str = oidc_util_hdr_in_x_forwarded_proto_get(r); /* if not we'll determine the scheme used to connect to this server */ if (scheme_str == NULL) { #ifdef APACHE2_0 scheme_str = (char *) ap_http_method(r); #else scheme_str = (char *) ap_http_scheme(r); #endif } if ((scheme_str == NULL) || ((apr_strnatcmp(scheme_str, "http") != 0) && (apr_strnatcmp(scheme_str, "https") != 0))) { oidc_warn(r, "detected HTTP scheme \"%s\" is not \"http\" nor \"https\"; perhaps your reverse proxy passes a wrongly configured \"%s\" header: falling back to default \"https\"", scheme_str, OIDC_HTTP_HDR_X_FORWARDED_PROTO); scheme_str = "https"; } return scheme_str; } /* * get the URL port that is currently being accessed */ static const char *oidc_get_current_url_port(const request_rec *r, const char *scheme_str) { /* * first see if there's a proxy/load-balancer in front of us * that sets X-Forwarded-Port */ const char *port_str = oidc_util_hdr_in_x_forwarded_port_get(r); if (port_str) return port_str; /* * see if we can get the port from the "X-Forwarded-Host" header * and if that header was set we'll assume defaults */ const char *host_hdr = oidc_util_hdr_in_x_forwarded_host_get(r); if (host_hdr) { port_str = strchr(host_hdr, OIDC_CHAR_COLON); if (port_str) port_str++; return port_str; } /* * see if we can get the port from the "Host" header; if not * we'll determine the port locally */ host_hdr = oidc_util_hdr_in_host_get(r); if (host_hdr) { port_str = strchr(host_hdr, OIDC_CHAR_COLON); if (port_str) { port_str++; return port_str; } } /* * if X-Forwarded-Proto assume the default port otherwise the * port should have been set in the X-Forwarded-Port header */ if (oidc_util_hdr_in_x_forwarded_proto_get(r)) return NULL; /* * if no port was set in the Host header and no X-Forwarded-Proto was set, we'll * determine the port locally and don't print it when it's the default for the protocol */ const apr_port_t port = r->connection->local_addr->port; if ((apr_strnatcmp(scheme_str, "https") == 0) && port == 443) return NULL; else if ((apr_strnatcmp(scheme_str, "http") == 0) && port == 80) return NULL; port_str = apr_psprintf(r->pool, "%u", port); return port_str; } /* * get the hostname part of the URL that is currently being accessed */ const char *oidc_get_current_url_host(request_rec *r) { const char *host_str = oidc_util_hdr_in_x_forwarded_host_get(r); if (host_str == NULL) host_str = oidc_util_hdr_in_host_get(r); if (host_str) { host_str = apr_pstrdup(r->pool, host_str); char *p = strchr(host_str, OIDC_CHAR_COLON); if (p != NULL) *p = '\0'; } else { /* no Host header, HTTP 1.0 */ host_str = ap_get_server_name(r); } return host_str; } /* * get the base part of the current URL (scheme + host (+ port)) */ static const char *oidc_get_current_url_base(request_rec *r) { const char *scheme_str = oidc_get_current_url_scheme(r); const char *host_str = oidc_get_current_url_host(r); const char *port_str = oidc_get_current_url_port(r, scheme_str); port_str = port_str ? apr_psprintf(r->pool, ":%s", port_str) : ""; char *url = apr_pstrcat(r->pool, scheme_str, "://", host_str, port_str, NULL); return url; } /* * get the URL that is currently being accessed */ char *oidc_get_current_url(request_rec *r) { char *url = apr_pstrcat(r->pool, oidc_get_current_url_base(r), r->uri, (r->args != NULL && *r->args != '\0' ? "?" : ""), r->args, NULL); oidc_debug(r, "current URL '%s'", url); return url; } /* * determine absolute redirect uri */ const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *cfg) { char *redirect_uri = cfg->redirect_uri; if ((redirect_uri != NULL) && (redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH)) { // relative redirect uri redirect_uri = apr_pstrcat(r->pool, oidc_get_current_url_base(r), cfg->redirect_uri, NULL); oidc_debug(r, "determined absolute redirect uri: %s", redirect_uri); } return redirect_uri; } /* * determine absolute redirect uri that is issuer specific */ const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider) { const char *redirect_uri = oidc_get_redirect_uri(r, cfg); if (provider->issuer_specific_redirect_uri != 0) { redirect_uri = apr_psprintf(r->pool, "%s%s%s=%s", redirect_uri, strchr(redirect_uri ? redirect_uri : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY, OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer)); // OIDC_PROTO_CLIENT_ID, // oidc_util_escape_string(r, provider->client_id)); oidc_debug(r, "determined issuer specific redirect uri: %s", redirect_uri); } return redirect_uri; } /* buffer to hold HTTP call responses */ typedef struct oidc_curl_buffer { request_rec *r; char *memory; size_t size; } oidc_curl_buffer; /* maximum acceptable size of HTTP responses: 1 Mb */ #define OIDC_CURL_MAX_RESPONSE_SIZE 1024 * 1024 /* * callback for CURL to write bytes that come back from an HTTP call */ size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; oidc_curl_buffer *mem = (oidc_curl_buffer *) userp; /* check if we don't run over the maximum buffer/memory size for HTTP responses */ if (mem->size + realsize > OIDC_CURL_MAX_RESPONSE_SIZE) { oidc_error(mem->r, "HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d", mem->size, realsize, OIDC_CURL_MAX_RESPONSE_SIZE); return 0; } /* allocate the new buffer for the current + new response bytes */ char *newptr = apr_palloc(mem->r->pool, mem->size + realsize + 1); if (newptr == NULL) { oidc_error(mem->r, "memory allocation for new buffer of %ld bytes failed", mem->size + realsize + 1); return 0; } /* copy over the data from current memory plus the cURL buffer */ memcpy(newptr, mem->memory, mem->size); memcpy(&(newptr[mem->size]), contents, realsize); mem->size += realsize; mem->memory = newptr; mem->memory[mem->size] = 0; return realsize; } /* context structure for encoding parameters */ typedef struct oidc_http_encode_t { request_rec *r; char *encoded_params; } oidc_http_encode_t; /* * add a url-form-encoded name/value pair */ static int oidc_util_http_add_form_url_encoded_param(void* rec, const char* key, const char* value) { oidc_http_encode_t *ctx = (oidc_http_encode_t*) rec; oidc_debug(ctx->r, "processing: %s=%s", key, value); const char *sep = ctx->encoded_params ? OIDC_STR_AMP : ""; ctx->encoded_params = apr_psprintf(ctx->r->pool, "%s%s%s=%s", ctx->encoded_params ? ctx->encoded_params : "", sep, oidc_util_escape_string(ctx->r, key), oidc_util_escape_string(ctx->r, value)); return 1; } /* * construct a URL with query parameters */ char *oidc_util_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params) { char *result = NULL; if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { oidc_http_encode_t data = { r, NULL }; apr_table_do(oidc_util_http_add_form_url_encoded_param, &data, params, NULL); const char *sep = NULL; if (data.encoded_params) sep = strchr(url ? url : "", OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY; result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "", data.encoded_params ? data.encoded_params : ""); } else { result = apr_pstrdup(r->pool, url); } oidc_debug(r, "url=%s", result); return result; } /* * construct form-encoded POST data */ static char *oidc_util_http_form_encoded_data(request_rec *r, const apr_table_t *params) { char *data = NULL; if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { oidc_http_encode_t encode_data = { r, NULL }; apr_table_do(oidc_util_http_add_form_url_encoded_param, &encode_data, params, NULL); data = encode_data.encoded_params; } oidc_debug(r, "data=%s", data); return data; } /* * execute a HTTP (GET or POST) request */ static apr_byte_t oidc_util_http_call(request_rec *r, const char *url, const char *data, const char *content_type, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key) { char curlError[CURL_ERROR_SIZE]; oidc_curl_buffer curlBuffer; CURL *curl; struct curl_slist *h_list = NULL; int i; /* do some logging about the inputs */ oidc_debug(r, "url=%s, data=%s, content_type=%s, basic_auth=%s, bearer_token=%s, ssl_validate_server=%d, timeout=%d, outgoing_proxy=%s, pass_cookies=%pp, ssl_cert=%s, ssl_key=%s", url, data, content_type, basic_auth, bearer_token, ssl_validate_server, timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key); curl = curl_easy_init(); if (curl == NULL) { oidc_error(r, "curl_easy_init() error"); return FALSE; } /* set the error buffer as empty before performing a request */ curlError[0] = 0; /* some of these are not really required */ curl_easy_setopt(curl, CURLOPT_HEADER, 0L); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); /* set the timeout */ curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); /* setup the buffer where the response will be written to */ curlBuffer.r = r; curlBuffer.memory = NULL; curlBuffer.size = 0; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void * )&curlBuffer); #ifndef LIBCURL_NO_CURLPROTO curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); #endif /* set the options for validating the SSL server certificate that the remote site presents */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (ssl_validate_server != FALSE ? 1L : 0L)); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (ssl_validate_server != FALSE ? 2L : 0L)); #ifdef WIN32 DWORD buflen; char *ptr = NULL; char *retval = (char *) malloc(sizeof (TCHAR) * (MAX_PATH + 1)); retval[0] = '\0'; buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH+1, retval, &ptr); if (buflen > 0) curl_easy_setopt(curl, CURLOPT_CAINFO, retval); else oidc_warn(r, "no curl-ca-bundle.crt file found in path"); free(retval); #endif /* identify this HTTP client */ curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_auth_openidc"); /* set optional outgoing proxy for the local network */ if (outgoing_proxy) { curl_easy_setopt(curl, CURLOPT_PROXY, outgoing_proxy); } /* see if we need to add token in the Bearer Authorization header */ if (bearer_token != NULL) { h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "Authorization: Bearer %s", bearer_token)); } /* see if we need to perform HTTP basic authentication to the remote site */ if (basic_auth != NULL) { curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_easy_setopt(curl, CURLOPT_USERPWD, basic_auth); } if (ssl_cert != NULL) curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl_cert); if (ssl_key != NULL) curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl_key); if (data != NULL) { /* set POST data */ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); /* set HTTP method to POST */ curl_easy_setopt(curl, CURLOPT_POST, 1); } if (content_type != NULL) { /* set content type */ h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE, content_type)); } /* see if we need to add any custom headers */ if (h_list != NULL) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h_list); if (pass_cookies != NULL) { /* gather cookies that we need to pass on from the incoming request */ char *cookie_string = NULL; for (i = 0; i < pass_cookies->nelts; i++) { const char *cookie_name = ((const char**) pass_cookies->elts)[i]; char *cookie_value = oidc_util_get_cookie(r, cookie_name); if (cookie_value != NULL) { cookie_string = (cookie_string == NULL) ? apr_psprintf(r->pool, "%s=%s", cookie_name, cookie_value) : apr_psprintf(r->pool, "%s; %s=%s", cookie_string, cookie_name, cookie_value); } } /* see if we need to pass any cookies */ if (cookie_string != NULL) { oidc_debug(r, "passing browser cookies on backend call: %s", cookie_string); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie_string); } } /* set the target URL */ curl_easy_setopt(curl, CURLOPT_URL, url); /* call it and record the result */ int rv = TRUE; if (curl_easy_perform(curl) != CURLE_OK) { oidc_error(r, "curl_easy_perform() failed on: %s (%s)", url, curlError[0] ? curlError : ""); rv = FALSE; goto out; } long response_code; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); oidc_debug(r, "HTTP response code=%ld", response_code); *response = apr_pstrmemdup(r->pool, curlBuffer.memory, curlBuffer.size); /* set and log the response */ oidc_debug(r, "response=%s", *response ? *response : ""); out: /* cleanup and return the result */ if (h_list != NULL) curl_slist_free_all(h_list); curl_easy_cleanup(curl); return rv; } /* * execute HTTP GET request */ apr_byte_t oidc_util_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key) { char *query_url = oidc_util_http_query_encoded_url(r, url, params); return oidc_util_http_call(r, query_url, NULL, NULL, basic_auth, bearer_token, ssl_validate_server, response, timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key); } /* * execute HTTP POST request with form-encoded data */ apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key) { char *data = oidc_util_http_form_encoded_data(r, params); return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_FORM_ENCODED, basic_auth, bearer_token, ssl_validate_server, response, timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key); } /* * execute HTTP POST request with JSON-encoded data */ apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url, json_t *json, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key) { char *data = json != NULL ? oidc_util_encode_json_object(r, json, JSON_COMPACT) : NULL; return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_JSON, basic_auth, bearer_token, ssl_validate_server, response, timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key); } /* * get the current path from the request in a normalized way */ static char *oidc_util_get_path(request_rec *r) { size_t i; char *p; p = r->parsed_uri.path; if ((p == NULL) || (p[0] == '\0')) return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH); for (i = strlen(p) - 1; i > 0; i--) if (p[i] == OIDC_CHAR_FORWARD_SLASH) break; return apr_pstrndup(r->pool, p, i + 1); } /* * get the cookie path setting and check that it matches the request path; cook it up if it is not set */ static char *oidc_util_get_cookie_path(request_rec *r) { char *rv = NULL, *requestPath = oidc_util_get_path(r); char *cookie_path = oidc_cfg_dir_cookie_path(r); if (cookie_path != NULL) { if (strncmp(cookie_path, requestPath, strlen(cookie_path)) == 0) rv = cookie_path; else { oidc_warn(r, "" OIDCCookiePath " (%s) is not a substring of request path, using request path (%s) for cookie", cookie_path, requestPath); rv = requestPath; } } else { rv = requestPath; } return (rv); } #define OIDC_COOKIE_FLAG_DOMAIN "Domain" #define OIDC_COOKIE_FLAG_PATH "Path" #define OIDC_COOKIE_FLAG_EXPIRES "Expires" #define OIDC_COOKIE_FLAG_SECURE "Secure" #define OIDC_COOKIE_FLAG_HTTP_ONLY "HttpOnly" #define OIDC_COOKIE_MAX_SIZE 4093 /* * set a cookie in the HTTP response headers */ void oidc_util_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); char *headerString, *expiresString = NULL; /* see if we need to clear the cookie */ if (apr_strnatcmp(cookieValue, "") == 0) expires = 0; /* construct the expire value */ if (expires != -1) { expiresString = (char *) apr_pcalloc(r->pool, APR_RFC822_DATE_LEN); if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) { oidc_error(r, "could not set cookie expiry date"); } } /* construct the cookie value */ headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue); headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_COOKIE_FLAG_PATH, oidc_util_get_cookie_path(r)); if (expiresString != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_COOKIE_FLAG_EXPIRES, expiresString); if (c->cookie_domain != NULL) headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_COOKIE_FLAG_DOMAIN, c->cookie_domain); if (apr_strnatcasecmp("https", oidc_get_current_url_scheme(r)) == 0) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_COOKIE_FLAG_SECURE); if (c->cookie_http_only != FALSE) headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_COOKIE_FLAG_HTTP_ONLY); if (ext != NULL) headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext); /* sanity check on overall cookie value size */ if (strlen(headerString) > OIDC_COOKIE_MAX_SIZE) { oidc_warn(r, "the length of the cookie value (%d) is greater than %d(!) bytes, this may not work with all browsers/server combinations: consider switching to a server side caching!", (int )strlen(headerString), OIDC_COOKIE_MAX_SIZE); } /* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only prints on 2xx responses */ oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString); } /* * get a cookie from the HTTP request */ char *oidc_util_get_cookie(request_rec *r, const char *cookieName) { char *cookie, *tokenizerCtx, *rv = NULL; /* get the Cookie value */ char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r)); if (cookies != NULL) { /* tokenize on ; to find the cookie we want */ cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx); while (cookie != NULL) { while (*cookie == OIDC_CHAR_SPACE) cookie++; /* see if we've found the cookie that we're looking for */ if ((strncmp(cookie, cookieName, strlen(cookieName)) == 0) && (cookie[strlen(cookieName)] == OIDC_CHAR_EQUAL)) { /* skip to the meat of the parameter (the value after the '=') */ cookie += (strlen(cookieName) + 1); rv = apr_pstrdup(r->pool, cookie); break; } /* go to the next cookie */ cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); } } /* log what we've found */ oidc_debug(r, "returning \"%s\" = %s", cookieName, rv ? apr_psprintf(r->pool, "\"%s\"", rv) : ""); return rv; } #define OIDC_COOKIE_CHUNKS_SEPARATOR "_" #define OIDC_COOKIE_CHUNKS_POSTFIX "chunks" /* * get the name of the cookie that contains the number of chunks */ static char *oidc_util_get_chunk_count_name(request_rec *r, const char *cookieName) { return apr_psprintf(r->pool, "%s%s%s", cookieName, OIDC_COOKIE_CHUNKS_SEPARATOR, OIDC_COOKIE_CHUNKS_POSTFIX); } /* * get the number of cookie chunks set by the browser */ static int oidc_util_get_chunked_count(request_rec *r, const char *cookieName) { int chunkCount = 0; char* chunkCountValue = oidc_util_get_cookie(r, oidc_util_get_chunk_count_name(r, cookieName)); if (chunkCountValue != NULL) { char *endptr = NULL; chunkCount = strtol(chunkCountValue, &endptr, 10); if ((*chunkCountValue == '\0') || (*endptr != '\0')) chunkCount = 0; } return chunkCount; } /* * get the name of a chunk */ static char *oidc_util_get_chunk_cookie_name(request_rec *r, const char *cookieName, int i) { return apr_psprintf(r->pool, "%s%s%d", cookieName, OIDC_COOKIE_CHUNKS_SEPARATOR, i); } /* * get a cookie value that is split over a number of chunked cookies */ char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize) { char *cookieValue = NULL; char *chunkValue = NULL; int i = 0; if (chunkSize == 0) { cookieValue = oidc_util_get_cookie(r, cookieName); } else { int chunkCount = oidc_util_get_chunked_count(r, cookieName); if (chunkCount > 0) { cookieValue = ""; for (i = 0; i < chunkCount; i++) { chunkValue = oidc_util_get_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i)); if (chunkValue != NULL) cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue, chunkValue); } } else { cookieValue = oidc_util_get_cookie(r, cookieName); } } return cookieValue; } /* * set a cookie value that is split over a number of chunked cookies */ void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext) { int i = 0; int cookieLength = strlen(cookieValue); char *chunkCountName = oidc_util_get_chunk_count_name(r, cookieName); char *chunkValue = NULL; /* see if we need to chunk at all */ if ((chunkSize == 0) || ((cookieLength > 0) && (cookieLength < chunkSize))) { oidc_util_set_cookie(r, cookieName, cookieValue, expires, ext); return; } /* see if we need to clear a possibly chunked cookie */ if (cookieLength == 0) { int chunkCount = oidc_util_get_chunked_count(r, cookieName); if (chunkCount > 0) { for (i = 0; i < chunkCount; i++) oidc_util_set_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i), "", expires, ext); oidc_util_set_cookie(r, chunkCountName, "", expires, ext); } else { oidc_util_set_cookie(r, cookieName, "", expires, ext); } return; } /* set a chunked cookie */ int chunkCountValue = cookieLength / chunkSize + 1; const char *ptr = cookieValue; for (i = 0; i < chunkCountValue; i++) { chunkValue = apr_pstrndup(r->pool, ptr, chunkSize); ptr += chunkSize; oidc_util_set_cookie(r, oidc_util_get_chunk_cookie_name(r, cookieName, i), chunkValue, expires, ext); }; oidc_util_set_cookie(r, chunkCountName, apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext); } /* * normalize a string for use as an HTTP Header Name. Any invalid * characters (per http://tools.ietf.org/html/rfc2616#section-4.2 and * http://tools.ietf.org/html/rfc2616#section-2.2) are replaced with * a dash ('-') character. */ char *oidc_normalize_header_name(const request_rec *r, const char *str) { /* token = 1* * CTL = * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ const char *separators = "()<>@,;:\\\"/[]?={} \t"; char *ns = apr_pstrdup(r->pool, str); size_t i; for (i = 0; i < strlen(ns); i++) { if (ns[i] < 32 || ns[i] == 127) ns[i] = '-'; else if (strchr(separators, ns[i]) != NULL) ns[i] = '-'; } return ns; } /* * see if the currently accessed path matches a path from a defined URL */ apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url) { apr_uri_t uri; memset(&uri, 0, sizeof(apr_uri_t)); if ((url == NULL) || (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS)) return FALSE; oidc_debug(r, "comparing \"%s\"==\"%s\"", r->parsed_uri.path, uri.path); if ((r->parsed_uri.path == NULL) || (uri.path == NULL)) return (r->parsed_uri.path == uri.path); return (apr_strnatcmp(r->parsed_uri.path, uri.path) == 0); } /* * see if the currently accessed path has a certain query parameter */ apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char* param) { if (r->args == NULL) return FALSE; const char *option1 = apr_psprintf(r->pool, "%s=", param); const char *option2 = apr_psprintf(r->pool, "&%s=", param); return ((strstr(r->args, option1) == r->args) || (strstr(r->args, option2) != NULL)) ? TRUE : FALSE; } /* * get a query parameter */ apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name, char **value) { char *tokenizer_ctx, *p, *args; const char *k_param = apr_psprintf(r->pool, "%s=", name); const size_t k_param_sz = strlen(k_param); *value = NULL; if (r->args == NULL || strlen(r->args) == 0) return FALSE; /* not sure why we do this, but better be safe than sorry */ args = apr_pstrmemdup(r->pool, r->args, strlen(r->args)); p = apr_strtok(args, OIDC_STR_AMP, &tokenizer_ctx); do { if (p && strncmp(p, k_param, k_param_sz) == 0) { *value = apr_pstrdup(r->pool, p + k_param_sz); *value = oidc_util_unescape_string(r, *value); } p = apr_strtok(NULL, OIDC_STR_AMP, &tokenizer_ctx); } while (p); return (*value != NULL ? TRUE : FALSE); } /* * printout a JSON string value */ static apr_byte_t oidc_util_json_string_print(request_rec *r, json_t *result, const char *key, const char *log) { json_t *value = json_object_get(result, key); if (value != NULL && !json_is_null(value)) { oidc_error(r, "%s: response contained an \"%s\" entry with value: \"%s\"", log, key, oidc_util_encode_json_object(r, value, JSON_ENCODE_ANY)); return TRUE; } return FALSE; } /* * check a JSON object for "error" results and printout */ static apr_byte_t oidc_util_check_json_error(request_rec *r, json_t *json) { if (oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR, "oidc_util_check_json_error") == TRUE) { oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR_DESCRIPTION, "oidc_util_check_json_error"); return TRUE; } return FALSE; } /* * parse a JSON object */ apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json) { if (str == NULL) return FALSE; json_error_t json_error; *json = json_loads(str, 0, &json_error); /* decode the JSON contents of the buffer */ if (*json == NULL) { /* something went wrong */ oidc_error(r, "JSON parsing returned an error: %s (%s)", json_error.text, str); return FALSE; } if (!json_is_object(*json)) { /* oops, no JSON */ oidc_error(r, "parsed JSON did not contain a JSON object"); json_decref(*json); *json = NULL; return FALSE; } return TRUE; } /* * encode a JSON object */ char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags) { char *s = json_dumps(json, flags); char *s_value = apr_pstrdup(r->pool, s); free(s); return s_value; } /* * decode a JSON string, check for "error" results and printout */ apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json) { if (oidc_util_decode_json_object(r, str, json) == FALSE) return FALSE; // see if it is not an error response somehow if (oidc_util_check_json_error(r, *json) == TRUE) { json_decref(*json); *json = NULL; return FALSE; } return TRUE; } /* * sends content to the user agent */ int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue) { ap_set_content_type(r, content_type); apr_bucket_brigade *bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_bucket *b = apr_bucket_transient_create(data, data_len, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); int rc = ap_pass_brigade(r->output_filters, bb); if (rc != APR_SUCCESS) { oidc_error(r, "ap_pass_brigade returned an error: %d; if you're using this module combined with mod_deflate try make an exception for the " OIDCRedirectURI " e.g. using SetEnvIf Request_URI no-gzip", rc); return HTTP_INTERNAL_SERVER_ERROR; } //r->status = success_rvalue; return success_rvalue; } /* * send HTML content to the user agent */ int oidc_util_html_send(request_rec *r, const char *title, const char *html_head, const char *on_load, const char *html_body, int status_code) { char *html = "\n" "\n" " \n" " \n" " %s\n" " %s\n" " \n" " \n" "%s\n" " \n" "\n"; html = apr_psprintf(r->pool, html, title ? oidc_util_html_escape(r->pool, title) : "", html_head ? html_head : "", on_load ? apr_psprintf(r->pool, " onload=\"%s()\"", on_load) : "", html_body ? html_body : "

"); return oidc_util_http_send(r, html, strlen(html), OIDC_CONTENT_TYPE_HTML, status_code); } static char *html_error_template_contents = NULL; /* * get the full path to a file based on an (already) absolute filename or a filename * that is relative to the Apache root directory */ char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename) { return (abs_or_rel_filename) ? ap_server_root_relative(pool, abs_or_rel_filename) : NULL; } /* * send a user-facing error to the browser */ int oidc_util_html_send_error(request_rec *r, const char *html_template, const char *error, const char *description, int status_code) { char *html = ""; if (html_template != NULL) { html_template = oidc_util_get_full_path(r->pool, html_template); if (html_error_template_contents == NULL) { int rc = oidc_util_file_read(r, html_template, r->server->process->pool, &html_error_template_contents); if (rc == FALSE) { oidc_error(r, "could not read HTML error template: %s", html_template); html_error_template_contents = NULL; } } if (html_error_template_contents) { html = apr_psprintf(r->pool, html_error_template_contents, oidc_util_html_escape(r->pool, error ? error : ""), oidc_util_html_escape(r->pool, description ? description : "")); return oidc_util_http_send(r, html, strlen(html), OIDC_CONTENT_TYPE_HTML, status_code); } } if (error != NULL) { html = apr_psprintf(r->pool, "%s

Error:

%s

", html, oidc_util_html_escape(r->pool, error)); } if (description != NULL) { html = apr_psprintf(r->pool, "%s

Description:

%s

", html, oidc_util_html_escape(r->pool, description)); } return oidc_util_html_send(r, "Error", NULL, NULL, html, status_code); } /* * read all bytes from the HTTP request */ static apr_byte_t oidc_util_read(request_rec *r, char **rbuf) { apr_size_t bytes_read; apr_size_t bytes_left; apr_size_t len; long read_length; if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK) return FALSE; len = ap_should_client_block(r) ? r->remaining : 0; if (len > OIDC_MAX_POST_DATA_LEN) { oidc_error(r, "POST parameter value is too large: %lu bytes (max=%d)", (unsigned long ) len, OIDC_MAX_POST_DATA_LEN); return FALSE; } *rbuf = (char *) apr_palloc(r->pool, len + 1); if (*rbuf == NULL) { oidc_error(r, "could not allocate memory for %lu bytes of POST data.", (unsigned long )len); return FALSE; } (*rbuf)[len] = '\0'; bytes_read = 0; bytes_left = len; while (bytes_left > 0) { read_length = ap_get_client_block(r, &(*rbuf)[bytes_read], bytes_left); if (read_length == 0) { (*rbuf)[bytes_read] = '\0'; break; } else if (read_length < 0) { oidc_error(r, "failed to read POST data from client"); return FALSE; } bytes_read += read_length; bytes_left -= read_length; } return TRUE; } /* * read form-encoded parameters from a string in to a table */ apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, char *data) { const char *key, *val, *p = data; while (p && *p && (val = ap_getword(r->pool, &p, OIDC_CHAR_AMP))) { key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL); key = oidc_util_unescape_string(r, key); val = oidc_util_unescape_string(r, val); oidc_debug(r, "read: %s=%s", key, val); apr_table_set(table, key, val); } oidc_debug(r, "parsed: %d bytes into %d elements", data ? (int )strlen(data) : 0, apr_table_elts(table)->nelts); return TRUE; } /* * read the POST parameters in to a table */ apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table) { char *data = NULL; if (r->method_number != M_POST) return FALSE; if (oidc_util_read(r, &data) != TRUE) return FALSE; return oidc_util_read_form_encoded_params(r, table, data); } /* * read a file from a path on disk */ apr_byte_t oidc_util_file_read(request_rec *r, const char *path, apr_pool_t *pool, char **result) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; char s_err[128]; apr_finfo_t finfo; /* open the file if it exists */ if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_warn(r, "no file found at: \"%s\"", path); return FALSE; } /* the file exists, now lock it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); /* move the read pointer to the very start of the cache file */ apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* get the file info so we know its size */ if ((rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, fd)) != APR_SUCCESS) { oidc_error(r, "error calling apr_file_info_get on file: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); goto error_close; } /* now that we have the size of the file, allocate a buffer that can contain its contents */ *result = apr_palloc(pool, finfo.size + 1); /* read the file in to the buffer */ apr_size_t bytes_read = 0; if ((rc = apr_file_read_full(fd, *result, finfo.size, &bytes_read)) != APR_SUCCESS) { oidc_error(r, "apr_file_read_full on (%s) returned an error: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); goto error_close; } /* just to be sure, we set a \0 (we allocated space for it anyway) */ (*result)[bytes_read] = '\0'; /* check that we've got all of it */ if (bytes_read != finfo.size) { oidc_error(r, "apr_file_read_full on (%s) returned less bytes (%" APR_SIZE_T_FMT ") than expected: (%" APR_OFF_T_FMT ")", path, bytes_read, finfo.size); goto error_close; } /* we're done, unlock and close the file */ apr_file_unlock(fd); apr_file_close(fd); /* log successful content retrieval */ oidc_debug(r, "file read successfully \"%s\"", path); return TRUE; error_close: apr_file_unlock(fd); apr_file_close(fd); oidc_error(r, "return error"); return FALSE; } /* * write data to a file */ apr_byte_t oidc_util_file_write(request_rec *r, const char *path, const char *data) { apr_file_t *fd = NULL; apr_status_t rc = APR_SUCCESS; apr_size_t bytes_written = 0; char s_err[128]; /* try to open the metadata file for writing, creating it if it does not exist */ if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE), APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { oidc_error(r, "file \"%s\" could not be opened (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* lock the file and move the write pointer to the start of it */ apr_file_lock(fd, APR_FLOCK_EXCLUSIVE); apr_off_t begin = 0; apr_file_seek(fd, APR_SET, &begin); /* calculate the length of the data, which is a string length */ apr_size_t len = strlen(data); /* (blocking) write the number of bytes in the buffer */ rc = apr_file_write_full(fd, data, len, &bytes_written); /* check for a system error */ if (rc != APR_SUCCESS) { oidc_error(r, "could not write to: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* check that all bytes from the header were written */ if (bytes_written != len) { oidc_error(r, "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")", path, bytes_written, len); return FALSE; } /* unlock and close the written file */ apr_file_unlock(fd); apr_file_close(fd); oidc_debug(r, "file \"%s\" written; number of bytes (%" APR_SIZE_T_FMT ")", path, len); return TRUE; } /* * see if two provided issuer identifiers match (cq. ignore trailing slash) */ apr_byte_t oidc_util_issuer_match(const char *a, const char *b) { /* check the "issuer" value against the one configure for the provider we got this id_token from */ if (apr_strnatcmp(a, b) != 0) { /* no strict match, but we are going to accept if the difference is only a trailing slash */ int n1 = strlen(a); int n2 = strlen(b); int n = ((n1 == n2 + 1) && (a[n1 - 1] == OIDC_CHAR_FORWARD_SLASH)) ? n2 : (((n2 == n1 + 1) && (b[n2 - 1] == OIDC_CHAR_FORWARD_SLASH)) ? n1 : 0); if ((n == 0) || (strncmp(a, b, n) != 0)) return FALSE; } return TRUE; } /* * see if a certain string value is part of a JSON array with string elements */ apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack, const char *needle) { if ((haystack == NULL) || (!json_is_array(haystack))) return FALSE; int i; for (i = 0; i < json_array_size(haystack); i++) { json_t *elem = json_array_get(haystack, i); if (!json_is_string(elem)) { oidc_error(r, "unhandled in-array JSON non-string object type [%d]", elem->type); continue; } if (apr_strnatcmp(json_string_value(elem), needle) == 0) { break; } } // oidc_debug(r, // "returning (%d=%d)", i, // haystack->value.array->nelts); return (i == json_array_size(haystack)) ? FALSE : TRUE; } /* * set a HTTP header and/or environment variable to pass information to the application */ void oidc_util_set_app_info(request_rec *r, const char *s_key, const char *s_value, const char *claim_prefix, apr_byte_t as_header, apr_byte_t as_env_var) { apr_table_t *env = NULL; /* construct the header name, cq. put the prefix in front of a normalized key name */ const char *s_name = apr_psprintf(r->pool, "%s%s", claim_prefix, oidc_normalize_header_name(r, s_key)); if (as_header) oidc_util_hdr_in_set(r, s_name, s_value); if (as_env_var) { /* do some logging about this event */ oidc_debug(r, "setting environment variable \"%s: %s\"", s_name, s_value); apr_pool_userdata_get((void **) &env, OIDC_USERDATA_ENV_KEY, r->pool); if (env == NULL) env = apr_table_make(r->pool, 10); apr_table_set(env, s_name, s_value); apr_pool_userdata_set(env, OIDC_USERDATA_ENV_KEY, NULL, r->pool); } } /* * set the user/claims information from the session in HTTP headers passed on to the application */ void oidc_util_set_app_infos(request_rec *r, const json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, apr_byte_t as_header, apr_byte_t as_env_var) { char s_int[255]; json_t *j_value = NULL; const char *s_key = NULL; /* if not attributes are set, nothing needs to be done */ if (j_attrs == NULL) { oidc_debug(r, "no attributes to set"); return; } /* loop over the claims in the JSON structure */ void *iter = json_object_iter((json_t*) j_attrs); while (iter) { /* get the next key/value entry */ s_key = json_object_iter_key(iter); j_value = json_object_iter_value(iter); // char *s_value= json_dumps(j_value, JSON_ENCODE_ANY); // oidc_util_set_app_info(r, s_key, s_value, claim_prefix); // free(s_value); /* check if it is a single value string */ if (json_is_string(j_value)) { /* set the single string in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, json_string_value(j_value), claim_prefix, as_header, as_env_var); } else if (json_is_boolean(j_value)) { /* set boolean value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, (json_is_true(j_value) ? "1" : "0"), claim_prefix, as_header, as_env_var); } else if (json_is_integer(j_value)) { if (sprintf(s_int, "%ld", (long) json_integer_value(j_value)) > 0) { /* set long value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, s_int, claim_prefix, as_header, as_env_var); } else { oidc_warn(r, "could not convert JSON number to string (> 255 characters?), skipping"); } } else if (json_is_real(j_value)) { /* set float value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, apr_psprintf(r->pool, "%lf", json_real_value(j_value)), claim_prefix, as_header, as_env_var); } else if (json_is_object(j_value)) { /* set json value in the application header whose name is based on the key and the prefix */ oidc_util_set_app_info(r, s_key, oidc_util_encode_json_object(r, j_value, 0), claim_prefix, as_header, as_env_var); /* check if it is a multi-value string */ } else if (json_is_array(j_value)) { /* some logging about what we're going to do */ oidc_debug(r, "parsing attribute array for key \"%s\" (#nr-of-elems: %llu)", s_key, (unsigned long long )json_array_size(j_value)); /* string to hold the concatenated array string values */ char *s_concat = apr_pstrdup(r->pool, ""); size_t i = 0; /* loop over the array */ for (i = 0; i < json_array_size(j_value); i++) { /* get the current element */ json_t *elem = json_array_get(j_value, i); /* check if it is a string */ if (json_is_string(elem)) { /* concatenate the string to the s_concat value using the configured separator char */ // TODO: escape the delimiter in the values (maybe reuse/extract url-formatted code from oidc_session_identity_encode) if (apr_strnatcmp(s_concat, "") != 0) { s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat, claim_delimiter, json_string_value(elem)); } else { s_concat = apr_psprintf(r->pool, "%s", json_string_value(elem)); } } else if (json_is_boolean(elem)) { if (apr_strnatcmp(s_concat, "") != 0) { s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat, claim_delimiter, json_is_true(elem) ? "1" : "0"); } else { s_concat = apr_psprintf(r->pool, "%s", json_is_true(elem) ? "1" : "0"); } } else { /* don't know how to handle a non-string array element */ oidc_warn(r, "unhandled in-array JSON object type [%d] for key \"%s\" when parsing claims array elements", elem->type, s_key); } } /* set the concatenated string */ oidc_util_set_app_info(r, s_key, s_concat, claim_prefix, as_header, as_env_var); } else { /* no string and no array, so unclear how to handle this */ oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\" when parsing claims", j_value->type, s_key); } iter = json_object_iter_next((json_t *) j_attrs, iter); } } /* * parse a space separated string in to a hash table */ apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str) { char *val; const char *data = apr_pstrdup(pool, str); apr_hash_t *result = apr_hash_make(pool); while (*data && (val = ap_getword_white(pool, &data))) { apr_hash_set(result, val, APR_HASH_KEY_STRING, val); } return result; } /* * compare two space separated value types */ apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b) { /* parse both entries as hash tables */ apr_hash_t *ht_a = oidc_util_spaced_string_to_hashtable(pool, a); apr_hash_t *ht_b = oidc_util_spaced_string_to_hashtable(pool, b); /* first compare the length of both response_types */ if (apr_hash_count(ht_a) != apr_hash_count(ht_b)) return FALSE; /* then loop over all entries */ apr_hash_index_t *hi; for (hi = apr_hash_first(NULL, ht_a); hi; hi = apr_hash_next(hi)) { const char *k; const char *v; apr_hash_this(hi, (const void**) &k, NULL, (void**) &v); if (apr_hash_get(ht_b, k, APR_HASH_KEY_STRING) == NULL) return FALSE; } /* if we've made it this far, a an b are equal in length and every element in a is in b */ return TRUE; } /* * see if a particular value is part of a space separated value */ apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match) { apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, str); return (apr_hash_get(ht, match, APR_HASH_KEY_STRING) != NULL); } /* * get (optional) string from a JSON object */ apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value) { *value = default_value ? apr_pstrdup(pool, default_value) : NULL; if (json != NULL) { json_t *v = json_object_get(json, name); if ((v != NULL) && (json_is_string(v))) { *value = apr_pstrdup(pool, json_string_value(v)); } } return TRUE; } /* * get (optional) int from a JSON object */ apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value) { *value = default_value; if (json != NULL) { json_t *v = json_object_get(json, name); if ((v != NULL) && (json_is_integer(v))) { *value = json_integer_value(v); } } return TRUE; } /* * get (optional) boolean from a JSON object */ apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value) { *value = default_value; if (json != NULL) { json_t *v = json_object_get(json, name); if ((v != NULL) && (json_is_boolean(v))) { *value = json_is_true(v); return TRUE; } } return FALSE; } /* * merge two JSON objects */ apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst) { const char *key; json_t *value = NULL; void *iter = NULL; if ((src == NULL) || (dst == NULL)) return FALSE; oidc_debug(r, "src=%s, dst=%s", oidc_util_encode_json_object(r, src, JSON_COMPACT), oidc_util_encode_json_object(r, dst, JSON_COMPACT)); iter = json_object_iter(src); while (iter) { key = json_object_iter_key(iter); value = json_object_iter_value(iter); json_object_set(dst, key, value); iter = json_object_iter_next(src, iter); } oidc_debug(r, "result dst=%s", oidc_util_encode_json_object(r, dst, JSON_COMPACT)); return TRUE; } /* * add query encoded parameters to a table */ void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params) { if (params != NULL) { const char *key, *val; const char *p = params; while (*p && (val = ap_getword(pool, &p, OIDC_CHAR_AMP))) { key = ap_getword(pool, &val, OIDC_CHAR_EQUAL); ap_unescape_url((char *) key); ap_unescape_url((char *) val); apr_table_add(table, key, val); } } } /* * create a symmetric key from a client_secret */ apr_byte_t oidc_util_create_symmetric_key(request_rec *r, const char *client_secret, unsigned int r_key_len, const char *hash_algo, apr_byte_t set_kid, oidc_jwk_t **jwk) { oidc_jose_error_t err; unsigned char *key = NULL; unsigned int key_len; if ((client_secret != NULL) && (strlen(client_secret) > 0)) { if (hash_algo == NULL) { key = (unsigned char *) client_secret; key_len = strlen(client_secret); } else { /* hash the client_secret first, this is OpenID Connect specific */ oidc_jose_hash_bytes(r->pool, hash_algo, (const unsigned char *) client_secret, strlen(client_secret), &key, &key_len, &err); } if ((key != NULL) && (key_len > 0)) { if ((r_key_len != 0) && (key_len >= r_key_len)) key_len = r_key_len; oidc_debug(r, "key_len=%d", key_len); *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, key, key_len, set_kid, &err); } if (*jwk == NULL) { oidc_error(r, "could not create JWK from the provided secret %s: %s", client_secret, oidc_jose_e2s(r->pool, err)); return FALSE; } } return TRUE; } /* * merge provided keys and client secret in to a single hashtable */ apr_hash_t * oidc_util_merge_symmetric_key(apr_pool_t *pool, apr_hash_t *keys, oidc_jwk_t *jwk) { apr_hash_t *result = (keys != NULL) ? apr_hash_copy(pool, keys) : apr_hash_make(pool); if (jwk != NULL) { apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk); } return result; } /* * openssl hash and base64 encode */ apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input, char **output) { oidc_jose_error_t err; unsigned char *hashed = NULL; unsigned int hashed_len = 0; if (oidc_jose_hash_bytes(r->pool, openssl_hash_algo, (const unsigned char *) input, strlen(input), &hashed, &hashed_len, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text); return FALSE; } if (oidc_base64url_encode(r, output, (const char *) hashed, hashed_len, TRUE) <= 0) { oidc_error(r, "oidc_base64url_encode returned an error: %s", err.text); return FALSE; } return TRUE; } /* * merge two key sets */ apr_hash_t * oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2) { if (k1 == NULL) { if (k2 == NULL) return apr_hash_make(pool); return k2; } if (k2 == NULL) return k1; return apr_hash_overlay(pool, k1, k2); } /* * regexp substitute * Example: * regex: "^.*([0-9]+).*$" * replace: "$1" * text_original: "match 292 numbers" * text_replaced: "292" */ apr_byte_t oidc_util_regexp_substitute( apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str) { const char *errorptr; int erroffset; pcre *preg; char *substituted; preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL); if (preg == NULL) { *error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression", regexp); pcre_free(preg); return FALSE; } substituted = pcre_subst(preg, NULL, input, (int) strlen(input), 0, 0, replace); if (substituted) { *output = apr_pstrdup(pool, substituted); pcre_free(preg); pcre_free(substituted); return TRUE; } else { *error_str = apr_psprintf(pool,"unknown error could not match string [%s] using pattern [%s] and replace matches in [%s]", input, regexp, replace); pcre_free(preg); } return FALSE; } /* * regexp match */ #define OIDC_UTIL_REGEXP_MATCH_SIZE 30 #define OIDC_UTIL_REGEXP_MATCH_NR 1 apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str) { const char *errorptr; int erroffset; pcre *preg; int subStr[OIDC_UTIL_REGEXP_MATCH_SIZE]; const char *psubStrMatchStr; preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL); if (preg == NULL) { *error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression", regexp); pcre_free(preg); return FALSE; } int rc = 0; if ((rc = pcre_exec(preg, NULL, input, (int) strlen(input), 0, 0, subStr, OIDC_UTIL_REGEXP_MATCH_SIZE)) < 0) { switch (rc) { case PCRE_ERROR_NOMATCH: *error_str = apr_pstrdup(pool, "string did not match the pattern"); break; case PCRE_ERROR_NULL: *error_str = apr_pstrdup(pool, "something was null"); break; case PCRE_ERROR_BADOPTION: *error_str = apr_pstrdup(pool, "a bad option was passed"); break; case PCRE_ERROR_BADMAGIC: *error_str = apr_pstrdup(pool, "magic number bad (compiled re corrupt?)"); break; case PCRE_ERROR_UNKNOWN_NODE: *error_str = apr_pstrdup(pool, "something kooky in the compiled re"); break; case PCRE_ERROR_NOMEMORY: *error_str = apr_pstrdup(pool, "ran out of memory"); break; default: *error_str = apr_psprintf(pool, "unknown error: %d", rc); break; } pcre_free(preg); return FALSE; } if (pcre_get_substring(input, subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR, &(psubStrMatchStr)) <= 0) { *error_str = apr_psprintf(pool, "pcre_get_substring failed (rc=%d)", rc); pcre_free(preg); return FALSE; } *output = apr_pstrdup(pool, psubStrMatchStr); pcre_free_substring(psubStrMatchStr); pcre_free(preg); return TRUE; } int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain) { char *p = NULL; char *check_cookie = cookie_domain; // Skip past the first char of a cookie_domain that starts // with a ".", ASCII 46 if (check_cookie[0] == 46) { p = strstr(hostname, ++check_cookie); } else { p = strstr(hostname, check_cookie); } if ((p == NULL) || (apr_strnatcmp(check_cookie, p) != 0)) { return FALSE; } return TRUE; } static const char *oidc_util_hdr_in_get(const request_rec *r, const char *name) { const char *value = apr_table_get(r->headers_in, name); if (value) oidc_debug(r, "%s=%s", name, value); return value; } static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) { char *last = NULL; const char *value = oidc_util_hdr_in_get(r, name); if (value) return apr_strtok(apr_pstrdup(r->pool, value), separator, &last); return NULL; } static void oidc_util_hdr_table_set(const request_rec *r, apr_table_t *table, const char *name, const char *value) { if (value != NULL) { char *s_value = apr_pstrdup(r->pool, value); /* * sanitize the header value by replacing line feeds with spaces * just like the Apache header input algorithms do for incoming headers * * this makes it impossible to have line feeds in values but that is * compliant with RFC 7230 (and impossible for regular headers due to Apache's * parsing of headers anyway) and fixes a security vulnerability on * overwriting/setting outgoing headers when used in proxy mode */ char *p = NULL; while ((p = strchr(s_value, '\n'))) *p = OIDC_CHAR_SPACE; oidc_debug(r, "%s: %s", name, s_value); apr_table_set(table, name, s_value); } else { oidc_debug(r, "unset %s", name); apr_table_unset(table, name); } } static void oidc_util_hdr_out_set(const request_rec *r, const char *name, const char *value) { oidc_util_hdr_table_set(r, r->headers_out, name, value); } static const char *oidc_util_hdr_out_get(const request_rec *r, const char *name) { return apr_table_get(r->headers_out, name); } void oidc_util_hdr_err_out_add(const request_rec *r, const char *name, const char *value) { oidc_debug(r, "%s: %s", name, value); apr_table_add(r->err_headers_out, name, value); } void oidc_util_hdr_in_set(const request_rec *r, const char *name, const char *value) { oidc_util_hdr_table_set(r, r->headers_in, name, value); } const char *oidc_util_hdr_in_cookie_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE); } void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value) { oidc_util_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value); } const char *oidc_util_hdr_in_user_agent_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT); } const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA); } const char *oidc_util_hdr_in_content_type_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE); } const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH); } const char *oidc_util_hdr_in_accept_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT); } const char *oidc_util_hdr_in_authorization_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION); } const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA); } const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA); } const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r) { return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA); } const char *oidc_util_hdr_in_host_get(const request_rec *r) { return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_HOST); } void oidc_util_hdr_out_location_set(const request_rec *r, const char *value) { oidc_util_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value); } const char *oidc_util_hdr_out_location_get(const request_rec *r) { return oidc_util_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION); } const char *oidc_util_get_provided_token_binding_id(const request_rec *r) { const char *result = NULL; if (r->subprocess_env != NULL) result = apr_table_get(r->subprocess_env, OIDC_TB_CFG_PROVIDED_ENV_VAR); return result; } mod_auth_openidc-2.3.3/src/authz.c0000644000076500000240000003033313200625410016737 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * mostly copied from mod_auth_cas * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include "mod_auth_openidc.h" #include #ifdef USE_LIBJQ #include "jq.h" #endif static apr_byte_t oidc_authz_match_value(request_rec *r, const char *spec_c, json_t *val, const char *key) { int i = 0; /* see if it is a string and it (case-insensitively) matches the Require'd value */ if (json_is_string(val)) { if (apr_strnatcmp(json_string_value(val), spec_c) == 0) return TRUE; /* see if it is a integer and it equals the Require'd value */ } else if (json_is_integer(val)) { if (json_integer_value(val) == atoi(spec_c)) return TRUE; /* see if it is a boolean and it (case-insensitively) matches the Require'd value */ } else if (json_is_boolean(val)) { if (apr_strnatcmp(json_is_true(val) ? "true" : "false", spec_c) == 0) return TRUE; /* if it is an array, we'll walk it */ } else if (json_is_array(val)) { /* compare the claim values */ for (i = 0; i < json_array_size(val); i++) { json_t *elem = json_array_get(val, i); if (json_is_string(elem)) { /* * approximately compare the claim value (ignoring * whitespace). At this point, spec_c points to the * NULL-terminated value pattern. */ if (apr_strnatcmp(json_string_value(elem), spec_c) == 0) return TRUE; } else if (json_is_boolean(elem)) { if (apr_strnatcmp( json_is_true(elem) ? "true" : "false", spec_c) == 0) return TRUE; } else if (json_is_integer(elem)) { if (json_integer_value(elem) == atoi(spec_c)) return TRUE; } else { oidc_warn(r, "unhandled in-array JSON object type [%d] for key \"%s\"", elem->type, (const char * ) key); } } } else { oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\"", val->type, (const char * ) key); } return FALSE; } static apr_byte_t oidc_authz_match_expression(request_rec *r, const char *spec_c, json_t *val) { const char *errorptr; int erroffset; pcre *preg; int i = 0; /* setup the regex; spec_c points to the NULL-terminated value pattern */ preg = pcre_compile(spec_c, 0, &errorptr, &erroffset, NULL); if (preg == NULL) { oidc_error(r, "pattern [%s] is not a valid regular expression", spec_c); pcre_free(preg); return FALSE; } /* see if the claim is a literal string */ if (json_is_string(val)) { /* PCRE-compare the string value against the expression */ if (pcre_exec(preg, NULL, json_string_value(val), (int) strlen(json_string_value(val)), 0, 0, NULL, 0) == 0) { pcre_free(preg); return TRUE; } /* see if the claim value is an array */ } else if (json_is_array(val)) { /* compare the claim values in the array against the expression */ for (i = 0; i < json_array_size(val); i++) { json_t *elem = json_array_get(val, i); if (json_is_string(elem)) { /* PCRE-compare the string value against the expression */ if (pcre_exec(preg, NULL, json_string_value(elem), (int) strlen(json_string_value(elem)), 0, 0, NULL, 0) == 0) { pcre_free(preg); return TRUE; } } } } pcre_free(preg); return FALSE; } /* * see if a the Require value matches with a set of provided claims */ apr_byte_t oidc_authz_match_claim(request_rec *r, const char * const attr_spec, const json_t * const claims) { const char *key; json_t *val; /* if we don't have any claims, they can never match any Require claim primitive */ if (claims == NULL) return FALSE; /* loop over all of the user claims */ void *iter = json_object_iter((json_t*) claims); while (iter) { key = json_object_iter_key(iter); val = json_object_iter_value(iter); oidc_debug(r, "evaluating key \"%s\"", (const char * ) key); const char *attr_c = (const char *) key; const char *spec_c = attr_spec; /* walk both strings until we get to the end of either or we find a differing character */ while ((*attr_c) && (*spec_c) && (*attr_c) == (*spec_c)) { attr_c++; spec_c++; } /* The match is a success if we walked the whole claim name and the attr_spec is at a colon. */ if (!(*attr_c) && (*spec_c) == OIDC_CHAR_COLON) { /* skip the colon */ spec_c++; if (oidc_authz_match_value(r, spec_c, val, key) == TRUE) return TRUE; /* a tilde denotes a string PCRE match */ } else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_TILDE) { /* skip the tilde */ spec_c++; if (oidc_authz_match_expression(r, spec_c, val) == TRUE) return TRUE; /* dot means child nodes must be evaluated */ } else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_DOT) { /* skip the dot */ spec_c++; if (!json_is_object(val)) { oidc_warn(r, "\"%s\" matched, and child nodes should be evaluated, but value is not an object.", key); return FALSE; } oidc_debug(r, "Attribute chunk matched. Evaluating children of key: \"%s\".", key); return oidc_authz_match_claim(r, spec_c, json_object_get(claims, key)); } iter = json_object_iter_next((json_t *) claims, iter); } return FALSE; } #ifdef USE_LIBJQ static apr_byte_t jq_parse(request_rec *r, jq_state *jq, struct jv_parser *parser) { apr_byte_t rv = FALSE; jv value; while (jv_is_valid((value = jv_parser_next(parser)))) { jq_start(jq, value, 0); jv result; while (jv_is_valid(result = jq_next(jq))) { jv dumped = jv_dump_string(result, 0); const char *str = jv_string_value(dumped); oidc_debug(r, "dumped: %s", str); rv = (apr_strnatcmp(str, "true") == 0); } jv_free(result); } if (jv_invalid_has_msg(jv_copy(value))) { jv msg = jv_invalid_get_msg(value); oidc_error(r, "invalid: %s", jv_string_value(msg)); jv_free(msg); rv = FALSE; } else { jv_free(value); } return rv; } /* * see if a the Require value matches a configured expression */ apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char * const attr_spec, const json_t * const claims) { apr_byte_t rv = FALSE; oidc_debug(r, "enter: '%s'", attr_spec); jq_state *jq = jq_init(); if (jq_compile(jq, attr_spec) == 0) { jq_teardown(&jq); return FALSE; } struct jv_parser *parser = jv_parser_new(0); char *buf = oidc_util_encode_json_object(r, (json_t *)claims, 0); jv_parser_set_buf(parser, buf, strlen(buf), 0); rv = jq_parse(r, jq, parser); jv_parser_free(parser); jq_teardown(&jq); return rv; } #endif #if MODULE_MAGIC_NUMBER_MAJOR < 20100714 /* * Apache <2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ int oidc_authz_worker22(request_rec *r, const json_t * const claims, const require_line * const reqs, int nelts) { const int m = r->method_number; const char *token; const char *requirement; int i; int have_oauthattr = 0; int count_oauth_claims = 0; oidc_authz_match_claim_fn_type match_claim_fn = NULL; /* go through applicable Require directives */ for (i = 0; i < nelts; ++i) { /* ignore this Require if it's in a section that exclude this method */ if (!(reqs[i].method_mask & (AP_METHOD_BIT << m))) { continue; } /* ignore if it's not a "Require claim ..." */ requirement = reqs[i].requirement; token = ap_getword_white(r->pool, &requirement); /* see if we've got anything meant for us */ if (apr_strnatcasecmp(token, OIDC_REQUIRE_CLAIM_NAME) == 0) { match_claim_fn = oidc_authz_match_claim; #ifdef USE_LIBJQ } else if (apr_strnatcasecmp(token, OIDC_REQUIRE_CLAIMS_EXPR_NAME) == 0) { match_claim_fn = oidc_authz_match_claims_expr; #endif } else { continue; } /* ok, we have a "Require claim/claims_expr" to satisfy */ have_oauthattr = 1; /* * If we have an applicable claim, but no claims were sent in the request, then we can * just stop looking here, because it's not satisfiable. The code after this loop will * give the appropriate response. */ if (!claims) { break; } /* * iterate over the claim specification strings in this require directive searching * for a specification that matches one of the claims/expressions. */ while (*requirement) { token = ap_getword_conf(r->pool, &requirement); count_oauth_claims++; oidc_debug(r, "evaluating claim/expr specification: %s", token); if (match_claim_fn(r, token, claims) == TRUE) { /* if *any* claim matches, then authorization has succeeded and all of the others are ignored */ oidc_debug(r, "require claim/expr '%s' matched", token); return OK; } } } /* if there weren't any "Require claim" directives, we're irrelevant */ if (!have_oauthattr) { oidc_debug(r, "no claim/expr statements found, not performing authz"); return DECLINED; } /* if there was a "Require claim", but no actual claims, that's cause to warn the admin of an iffy configuration */ if (count_oauth_claims == 0) { oidc_warn(r, "'require claim/expr' missing specification(s) in configuration, declining"); return DECLINED; } /* log the event, also in Apache speak */ oidc_debug(r, "authorization denied for client session"); ap_note_auth_failure(r); return HTTP_UNAUTHORIZED; } #else /* * Apache >=2.4 authorization routine: match the claims from the authenticated user against the Require primitive */ authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims, const char *require_args, oidc_authz_match_claim_fn_type match_claim_fn) { int count_oauth_claims = 0; const char *t, *w; /* needed for anonymous authentication */ if (r->user == NULL) return AUTHZ_DENIED_NO_USER; /* if no claims, impossible to satisfy */ if (!claims) return AUTHZ_DENIED; /* loop over the Required specifications */ t = require_args; while ((w = ap_getword_conf(r->pool, &t)) && w[0]) { count_oauth_claims++; oidc_debug(r, "evaluating claim/expr specification: %s", w); /* see if we can match any of out input claims against this Require'd value */ if (match_claim_fn(r, w, claims) == TRUE) { oidc_debug(r, "require claim/expr '%s' matched", w); return AUTHZ_GRANTED; } } /* if there wasn't anything after the Require claims directive... */ if (count_oauth_claims == 0) { oidc_warn(r, "'require claim/expr' missing specification(s) in configuration, denying"); } return AUTHZ_DENIED; } #endif mod_auth_openidc-2.3.3/src/session.c0000644000076500000240000005375613203320522017304 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include #include "mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; /* the name of the remote-user attribute in the session */ #define OIDC_SESSION_REMOTE_USER_KEY "r" /* the name of the session expiry attribute in the session */ #define OIDC_SESSION_EXPIRY_KEY "e" /* the name of the provided token binding attribute in the session */ #define OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY "ptb" static apr_byte_t oidc_session_encode(request_rec *r, oidc_cfg *c, oidc_session_t *z, char **s_value, apr_byte_t encrypt) { if (encrypt == FALSE) { *s_value = oidc_util_encode_json_object(r, z->state, JSON_COMPACT); return (*s_value != NULL); } if (oidc_util_jwt_create(r, c->crypto_passphrase, z->state, s_value) == FALSE) return FALSE; return TRUE; } static apr_byte_t oidc_session_decode(request_rec *r, oidc_cfg *c, oidc_session_t *z, const char *s_json, apr_byte_t encrypt) { if (encrypt == FALSE) { return oidc_util_decode_json_object(r, s_json, &z->state); } if (oidc_util_jwt_verify(r, c->crypto_passphrase, s_json, &z->state) == FALSE) { oidc_error(r, "could not verify secure JWT: cache value possibly corrupted"); return FALSE; } return TRUE; } /* * load the session from the cache using the cookie as the index */ static apr_byte_t oidc_session_load_cache(request_rec *r, oidc_session_t *z) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; /* get the cookie that should be our uuid/key */ char *uuid = oidc_util_get_cookie(r, oidc_cfg_dir_cookie(r)); /* get the string-encoded session from the cache based on the key; decryption is based on the cache backend config */ if (uuid != NULL) { char *s_json = NULL; rc = oidc_cache_get_session(r, uuid, &s_json); if ((rc == TRUE) && (s_json != NULL)) { rc = oidc_session_decode(r, c, z, s_json, FALSE); if (rc == TRUE) strncpy(z->uuid, uuid, strlen(uuid)); } } return rc; } /* * save the session to the cache using a cookie for the index */ static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = TRUE; if (z->state != NULL) { if (apr_strnatcmp(z->uuid, "") == 0) { /* get a new uuid for this session */ apr_uuid_t uuid; apr_uuid_get(&uuid); apr_uuid_format((char *) &z->uuid, &uuid); } /* store the string-encoded session in the cache; encryption depends on cache backend settings */ char *s_value = NULL; if (oidc_session_encode(r, c, z, &s_value, FALSE) == FALSE) return FALSE; rc = oidc_cache_set_session(r, z->uuid, s_value, z->expiry); if (rc == TRUE) /* set the uuid in the cookie */ oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), z->uuid, c->persistent_session_cookie ? z->expiry : -1, c->cookie_same_site ? (first_time ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_STRICT) : NULL); } else { /* clear the cookie */ oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0, NULL); /* remove the session from the cache */ rc = oidc_cache_set_session(r, z->uuid, NULL, 0); } return rc; } /* * load the session from a self-contained client-side cookie */ static apr_byte_t oidc_session_load_cookie(request_rec *r, oidc_cfg *c, oidc_session_t *z) { char *cookieValue = oidc_util_get_chunked_cookie(r, oidc_cfg_dir_cookie(r), c->session_cookie_chunk_size); if ((cookieValue != NULL) && (oidc_session_decode(r, c, z, cookieValue, TRUE) == FALSE)) return FALSE; return TRUE; } /* * store the session in a self-contained client-side-only cookie storage */ static apr_byte_t oidc_session_save_cookie(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); char *cookieValue = ""; if ((z->state != NULL) && (oidc_session_encode(r, c, z, &cookieValue, TRUE) == FALSE)) return FALSE; oidc_util_set_chunked_cookie(r, oidc_cfg_dir_cookie(r), cookieValue, c->persistent_session_cookie ? z->expiry : -1, c->session_cookie_chunk_size, c->cookie_same_site ? (first_time ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_STRICT) : NULL); return TRUE; } /* * load a session from the cache/cookie */ apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **zz) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; const char *ses_p_tb_id = NULL, *env_p_tb_id = NULL; /* allocate space for the session object and fill it */ oidc_session_t *z = (*zz = apr_pcalloc(r->pool, sizeof(oidc_session_t))); strncpy(z->uuid, "", strlen("")); z->remote_user = NULL; z->state = NULL; if (c->session_type == OIDC_SESSION_TYPE_SERVER_CACHE) /* load the session from the cache */ rc = oidc_session_load_cache(r, z); /* if we get here we configured client-cookie or retrieving from the cache failed */ if ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r))) /* load the session from a self-contained cookie */ rc = oidc_session_load_cookie(r, c, z); if (z->state != NULL) { json_t *j_expires = json_object_get(z->state, OIDC_SESSION_EXPIRY_KEY); if (j_expires) z->expiry = apr_time_from_sec(json_integer_value(j_expires)); /* check whether it has expired */ if (apr_time_now() > z->expiry) { oidc_warn(r, "session restored from cache has expired"); oidc_session_free(r, z); z->state = json_object(); } else { oidc_session_get(r, z, OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY, &ses_p_tb_id); if (ses_p_tb_id != NULL) { env_p_tb_id = oidc_util_get_provided_token_binding_id(r); if ((env_p_tb_id == NULL) || (apr_strnatcmp(env_p_tb_id, ses_p_tb_id) != 0)) { oidc_error(r, "the Provided Token Binding ID stored in the session doesn't match the one presented by the user agent"); oidc_session_free(r, z); z->state = json_object(); } } oidc_session_get(r, z, OIDC_SESSION_REMOTE_USER_KEY, &z->remote_user); } } else { z->state = json_object(); } return rc; } /* * save a session to cache/cookie */ apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first_time) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); apr_byte_t rc = FALSE; const char *p_tb_id = oidc_util_get_provided_token_binding_id(r); if (z->state != NULL) { oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user); json_object_set_new(z->state, OIDC_SESSION_EXPIRY_KEY, json_integer(apr_time_sec(z->expiry))); } if ((first_time) && (p_tb_id != NULL)) { oidc_debug(r, "Provided Token Binding ID environment variable found; adding its value to the session state"); oidc_session_set(r, z, OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY, p_tb_id); } if (c->session_type == OIDC_SESSION_TYPE_SERVER_CACHE) /* store the session in the cache */ rc = oidc_session_save_cache(r, z, first_time); /* if we get here we configured client-cookie or saving in the cache failed */ if ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE) || ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r))) /* store the session in a self-contained cookie */ rc = oidc_session_save_cookie(r, z, first_time); return rc; } /* * free resources allocated for a session */ apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z) { if (z->state) { json_decref(z->state); z->state = NULL; } z->expiry = 0; return TRUE; } /* * terminate a session */ apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z) { oidc_session_free(r, z); return oidc_session_save(r, z, FALSE); } /* * get a value from the session based on the name from a name/value pair */ apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key, const char **value) { /* just return the value for the key */ oidc_json_object_get_string(r->pool, z->state, key, (char **) value, NULL); return TRUE; } /* * set a name/value key pair in the session */ apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value) { /* only set it if non-NULL, otherwise delete the entry */ if (value) { json_object_set_new(z->state, key, json_string(value)); } else { json_object_del(z->state, key); } return TRUE; } /* * session object keys */ /* key for storing the userinfo claims in the session context */ #define OIDC_SESSION_KEY_USERINFO_CLAIMS "uic" /* key for storing the userinfo JWT in the session context */ #define OIDC_SESSION_KEY_USERINFO_JWT "uij" /* key for storing the id_token in the session context */ #define OIDC_SESSION_KEY_IDTOKEN_CLAIMS "idc" /* key for storing the raw id_token in the session context */ #define OIDC_SESSION_KEY_IDTOKEN "idt" /* key for storing the access_token in the session context */ #define OIDC_SESSION_KEY_ACCESSTOKEN "at" /* key for storing the access_token expiry in the session context */ #define OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES "ate" /* key for storing the refresh_token in the session context */ #define OIDC_SESSION_KEY_REFRESH_TOKEN "rt" /* key for storing maximum session duration in the session context */ #define OIDC_SESSION_KEY_SESSION_EXPIRES "se" /* key for storing the cookie domain in the session context */ #define OIDC_SESSION_KEY_COOKIE_DOMAIN "cd" /* key for storing last user info refresh timestamp in the session context */ #define OIDC_SESSION_KEY_USERINFO_LAST_REFRESH "uilr" /* key for storing last access token refresh timestamp in the session context */ #define OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH "atlr" /* key for storing request state */ #define OIDC_SESSION_KEY_REQUEST_STATE "rs" /* key for storing the original URL */ #define OIDC_SESSION_KEY_ORIGINAL_URL "ou" /* key for storing the session_state in the session context */ #define OIDC_SESSION_KEY_SESSION_STATE "ss" /* key for storing the issuer in the session context */ #define OIDC_SESSION_KEY_ISSUER "iss" /* key for storing the client_id in the session context */ #define OIDC_SESSION_KEY_CLIENT_ID "cid" /* key for storing the check_session_iframe in the session context */ #define OIDC_SESSION_KEY_CHECK_SESSION_IFRAME "csi" /* key for storing the end_session_endpoint in the session context */ #define OIDC_SESSION_KEY_LOGOUT_ENDPOINT "ese" /* * helper functions */ typedef const char *(*oidc_session_get_str_function)(request_rec *r, oidc_session_t *z); static void oidc_session_set_timestamp(request_rec *r, oidc_session_t *z, const char *key, const apr_time_t timestamp) { if (timestamp != -1) oidc_session_set(r, z, key, apr_psprintf(r->pool, "%" APR_TIME_T_FMT, timestamp)); } static json_t *oidc_session_get_str2json(request_rec *r, oidc_session_t *z, oidc_session_get_str_function session_get_str_fn) { json_t *json = NULL; const char *str = session_get_str_fn(r, z); if (str != NULL) oidc_util_decode_json_object(r, str, &json); return json; } static const char *oidc_session_get_key2string(request_rec *r, oidc_session_t *z, const char *key) { const char *s_value = NULL; oidc_session_get(r, z, key, &s_value); return s_value; } static apr_time_t oidc_session_get_key2timestamp(request_rec *r, oidc_session_t *z, const char *key) { apr_time_t t_expires = 0; const char *s_expires = oidc_session_get_key2string(r, z, key); if (s_expires != NULL) sscanf(s_expires, "%" APR_TIME_T_FMT, &t_expires); return t_expires; } void oidc_session_set_filtered_claims(request_rec *r, oidc_session_t *z, const char *session_key, const char *claims) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *name; json_t *src = NULL, *dst = NULL, *value = NULL; void *iter = NULL; apr_byte_t is_allowed; if (oidc_util_decode_json_object(r, claims, &src) == FALSE) return; dst = json_object(); iter = json_object_iter(src); while (iter) { is_allowed = TRUE; name = json_object_iter_key(iter); value = json_object_iter_value(iter); if ((c->black_listed_claims != NULL) && (apr_hash_get(c->black_listed_claims, name, APR_HASH_KEY_STRING) != NULL)) { oidc_debug(r, "removing blacklisted claim [%s]: '%s'", session_key, name); is_allowed = FALSE; } if ((is_allowed == TRUE) && (c->white_listed_claims != NULL) && (apr_hash_get(c->white_listed_claims, name, APR_HASH_KEY_STRING) == NULL)) { oidc_debug(r, "removing non-whitelisted claim [%s]: '%s'", session_key, name); is_allowed = FALSE; } if (is_allowed == TRUE) json_object_set(dst, name, value); iter = json_object_iter_next(src, iter); } char *filtered_claims = oidc_util_encode_json_object(r, dst, JSON_COMPACT); json_decref(dst); json_decref(src); oidc_session_set(r, z, session_key, filtered_claims); } /* * userinfo claims */ void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims) { oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS, claims); } const char * oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS); } json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z) { return oidc_session_get_str2json(r, z, oidc_session_get_userinfo_claims); } void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *s_userinfo_jwt) { oidc_session_set(r, z, OIDC_SESSION_KEY_USERINFO_JWT, s_userinfo_jwt); } const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_JWT); } /* * id_token claims */ void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, const char *idtoken_claims) { oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS, idtoken_claims); } const char * oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS); } json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z) { return oidc_session_get_str2json(r, z, oidc_session_get_idtoken_claims); } /* * compact serialized id_token */ void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token) { oidc_session_set(r, z, OIDC_SESSION_KEY_IDTOKEN, s_id_token); } const char * oidc_session_get_idtoken(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN); } /* * access token */ void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token) { oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN, access_token); } const char * oidc_session_get_access_token(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN); } /* * access token expires */ void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in) { if (expires_in != -1) { oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES, apr_psprintf(r->pool, "%" APR_TIME_T_FMT, apr_time_sec(apr_time_now()) + expires_in)); } } const char * oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES); } /* * refresh token */ void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token) { oidc_session_set(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN, refresh_token); } const char * oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN); } /* * session expires */ void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES, expires); } apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES); } /* * cookie domain */ void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain) { oidc_session_set(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN, cookie_domain); } const char * oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN); } /* * userinfo last refresh */ void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH, apr_time_now()); } apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH); } /* * access_token last refresh */ void oidc_session_reset_access_token_last_refresh(request_rec *r, oidc_session_t *z) { oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH, apr_time_now()); } apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH); } /* * request state */ void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state) { oidc_session_set(r, z, OIDC_SESSION_KEY_REQUEST_STATE, request_state); } const char * oidc_session_get_request_state(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REQUEST_STATE); } /* * original url */ void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url) { oidc_session_set(r, z, OIDC_SESSION_KEY_ORIGINAL_URL, original_url); } const char * oidc_session_get_original_url(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ORIGINAL_URL); } /* * session state */ void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state) { oidc_session_set(r, z, OIDC_SESSION_KEY_SESSION_STATE, session_state); } const char * oidc_session_get_session_state(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_SESSION_STATE); } /* * issuer */ void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer) { oidc_session_set(r, z, OIDC_SESSION_KEY_ISSUER, issuer); } const char * oidc_session_get_issuer(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ISSUER); } /* * client_id */ void oidc_session_set_client_id(request_rec *r, oidc_session_t *z, const char *client_id) { oidc_session_set(r, z, OIDC_SESSION_KEY_CLIENT_ID, client_id); } const char * oidc_session_get_client_id(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_CLIENT_ID); } /* * check session iframe URL */ void oidc_session_set_check_session_iframe(request_rec *r, oidc_session_t *z, const char *check_session_iframe) { oidc_session_set(r, z, OIDC_SESSION_KEY_CHECK_SESSION_IFRAME, check_session_iframe); } const char * oidc_session_get_check_session_iframe(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_CHECK_SESSION_IFRAME); } /* * logout endpoint URL */ void oidc_session_set_logout_endpoint(request_rec *r, oidc_session_t *z, const char *logout_endpoint) { oidc_session_set(r, z, OIDC_SESSION_KEY_LOGOUT_ENDPOINT, logout_endpoint); } const char * oidc_session_get_logout_endpoint(request_rec *r, oidc_session_t *z) { return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_LOGOUT_ENDPOINT); } mod_auth_openidc-2.3.3/src/metadata.c0000644000076500000240000014003313202535004017364 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * OpenID Connect metadata handling routines, for both OP discovery and client registration * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include #include #include #include #include #include "mod_auth_openidc.h" #include "parse.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; #define OIDC_METADATA_SUFFIX_PROVIDER "provider" #define OIDC_METADATA_SUFFIX_CLIENT "client" #define OIDC_METADATA_SUFFIX_CONF "conf" #define OIDC_METADATA_ISSUER "issuer" #define OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "response_types_supported" #define OIDC_METADATA_RESPONSE_MODES_SUPPORTED "response_modes_supported" #define OIDC_METADATA_AUTHORIZATION_ENDPOINT "authorization_endpoint" #define OIDC_METADATA_TOKEN_ENDPOINT "token_endpoint" #define OIDC_METADATA_USERINFO_ENDPOINT "userinfo_endpoint" #define OIDC_METADATA_JWKS_URI "jwks_uri" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "token_endpoint_auth_methods_supported" #define OIDC_METADATA_REGISTRATION_ENDPOINT "registration_endpoint" #define OIDC_METADATA_CHECK_SESSION_IFRAME "check_session_iframe" #define OIDC_METADATA_END_SESSION_ENDPOINT "end_session_endpoint" #define OIDC_METADATA_CLIENT_ID "client_id" #define OIDC_METADATA_CLIENT_SECRET "client_secret" #define OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "client_secret_expires_at" #define OIDC_METADATA_KEYS "keys" #define OIDC_METADATA_CLIENT_JWKS_URI "client_jwks_uri" #define OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG "id_token_signed_response_alg" #define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG "id_token_encrypted_response_alg" #define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC "id_token_encrypted_response_enc" #define OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG "userinfo_signed_response_alg" #define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG "userinfo_encrypted_response_alg" #define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC "userinfo_encrypted_response_enc" #define OIDC_METADATA_CLIENT_NAME "client_name" #define OIDC_METADATA_REDIRECT_URIS "redirect_uris" #define OIDC_METADATA_RESPONSE_TYPES "response_types" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "token_endpoint_auth_method" #define OIDC_METADATA_CONTACTS "contacts" #define OIDC_METADATA_INITIATE_LOGIN_URI "initiate_login_uri" #define OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI "frontchannel_logout_uri" #define OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS "post_logout_redirect_uris" #define OIDC_METADATA_SSL_VALIDATE_SERVER "ssl_validate_server" #define OIDC_METADATA_SCOPE "scope" #define OIDC_METADATA_JWKS_REFRESH_INTERVAL "jwks_refresh_interval" #define OIDC_METADATA_IDTOKEN_IAT_SLACK "idtoken_iat_slack" #define OIDC_METADATA_SESSION_MAX_DURATION "session_max_duration" #define OIDC_METADATA_AUTH_REQUEST_PARAMS "auth_request_params" #define OIDC_METADATA_TOKEN_ENDPOINT_PARAMS "token_endpoint_params" #define OIDC_METADATA_RESPONSE_MODE "response_mode" #define OIDC_METADATA_PKCE_METHOD "pkce_method" #define OIDC_METADATA_CLIENT_CONTACT "client_contact" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH "token_endpoint_auth" #define OIDC_METADATA_REGISTRATION_TOKEN "registration_token" #define OIDC_METADATA_REGISTRATION_ENDPOINT_JSON "registration_endpoint_json" #define OIDC_METADATA_RESPONSE_TYPE "response_type" #define OIDC_METADATA_USERINFO_REFRESH_INTERVAL "userinfo_refresh_interval" #define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT "token_endpoint_tls_client_cert" #define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY "token_endpoint_tls_client_key" #define OIDC_METADATA_REQUEST_OBJECT "request_object" #define OIDC_METADATA_USERINFO_TOKEN_METHOD "userinfo_token_method" #define OIDC_METADATA_TOKEN_BINDING_POLICY "token_binding_policy" #define OIDC_METADATA_AUTH_REQUEST_METHOD "auth_request_method" #define OIDC_METADATA_ISSUER_SPECIFIC_REDIRECT_URI "issuer_specific_redirect_uri" /* * get the metadata filename for a specified issuer (cq. urlencode it) */ static const char *oidc_metadata_issuer_to_filename(request_rec *r, const char *issuer) { /* strip leading https:// */ char *p = strstr(issuer, "https://"); if (p == issuer) { p = apr_pstrdup(r->pool, issuer + strlen("https://")); } else { p = strstr(issuer, "http://"); if (p == issuer) { p = apr_pstrdup(r->pool, issuer + strlen("http://")); } else { p = apr_pstrdup(r->pool, issuer); } } /* strip trailing '/' */ int n = strlen(p); if (p[n - 1] == OIDC_CHAR_FORWARD_SLASH) p[n - 1] = '\0'; return oidc_util_escape_string(r, p); } /* * get the issuer from a metadata filename (cq. urldecode it) */ static const char *oidc_metadata_filename_to_issuer(request_rec *r, const char *filename) { char *result = apr_pstrdup(r->pool, filename); char *p = strrchr(result, OIDC_CHAR_DOT); *p = '\0'; p = oidc_util_unescape_string(r, result); return apr_psprintf(r->pool, "https://%s", p); } /* * get the full path to the metadata file for a specified issuer and directory */ static const char *oidc_metadata_file_path(request_rec *r, oidc_cfg *cfg, const char *issuer, const char *type) { return apr_psprintf(r->pool, "%s/%s.%s", cfg->metadata_dir, oidc_metadata_issuer_to_filename(r, issuer), type); } /* * get the full path to the provider metadata file for a specified issuer */ static const char *oidc_metadata_provider_file_path(request_rec *r, const char *issuer) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_PROVIDER); } /* * get the full path to the client metadata file for a specified issuer */ static const char *oidc_metadata_client_file_path(request_rec *r, const char *issuer) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CLIENT); } /* * get the full path to the custom config file for a specified issuer */ static const char *oidc_metadata_conf_path(request_rec *r, const char *issuer) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CONF); } /* * get cache key for the JWKs file for a specified URI */ static const char *oidc_metadata_jwks_cache_key(request_rec *r, const char *jwks_uri) { return jwks_uri; } /* * read a JSON metadata file from disk */ static apr_byte_t oidc_metadata_file_read_json(request_rec *r, const char *path, json_t **result) { char *buf = NULL; /* read the file contents */ if (oidc_util_file_read(r, path, r->pool, &buf) == FALSE) return FALSE; /* decode the JSON contents of the buffer */ return oidc_util_decode_json_object(r, buf, result); } /* * check if the specified entry in metadata is a valid URI */ static apr_byte_t oidc_metadata_is_valid_uri(request_rec *r, const char *type, const char *issuer, json_t *json, const char *key, char **value, apr_byte_t is_mandatory) { char *s_value = NULL; oidc_json_object_get_string(r->pool, json, key, &s_value, NULL); if (s_value == NULL) { if (is_mandatory) { oidc_error(r, "%s (%s) JSON metadata does not contain the mandatory \"%s\" string entry", type, issuer, key); } return (!is_mandatory); } if (oidc_valid_http_url(r->pool, s_value) != NULL) { oidc_warn(r, "\"%s\" is not a valid http URL for key \"%s\"", s_value, key); return FALSE; } if (value) *value = s_value; return TRUE; } /* * check to see if JSON provider metadata is valid */ static apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg *cfg, json_t *j_provider, const char *issuer) { /* get the "issuer" from the provider metadata and double-check that it matches what we looked for */ char *s_issuer = NULL; oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &s_issuer, NULL); if (s_issuer == NULL) { oidc_error(r, "provider (%s) JSON metadata did not contain an \"" OIDC_METADATA_ISSUER "\" string", issuer); return FALSE; } /* check that the issuer matches */ if (issuer != NULL) { if (oidc_util_issuer_match(issuer, s_issuer) == FALSE) { oidc_error(r, "requested issuer (%s) does not match the \"" OIDC_METADATA_ISSUER "\" value in the provider metadata file: %s", issuer, s_issuer); return FALSE; } } /* verify that the provider supports the a flow that we implement */ if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_TYPES_SUPPORTED, oidc_valid_response_type, NULL, FALSE) != NULL) { if (json_object_get(j_provider, OIDC_METADATA_RESPONSE_TYPES_SUPPORTED) != NULL) { oidc_error(r, "could not find a supported response type in provider metadata (%s) for entry \"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\"", issuer); return FALSE; } oidc_warn(r, "could not find (required) supported response types (\"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\") in provider metadata (%s); assuming that \"code\" flow is supported...", issuer); } /* verify that the provider supports a response_mode that we implement */ if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_RESPONSE_MODES_SUPPORTED, oidc_valid_response_mode, NULL, TRUE) != NULL) { oidc_error(r, "could not find a supported response mode in provider metadata (%s) for entry \"" OIDC_METADATA_RESPONSE_MODES_SUPPORTED "\"", issuer); return FALSE; } /* check the required authorization endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_AUTHORIZATION_ENDPOINT, NULL, TRUE) == FALSE) return FALSE; /* check the optional token endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_TOKEN_ENDPOINT, NULL, FALSE) == FALSE) return FALSE; /* check the optional user info endpoint */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_USERINFO_ENDPOINT, NULL, FALSE) == FALSE) return FALSE; /* check the optional JWKs URI */ if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer, j_provider, OIDC_METADATA_JWKS_URI, NULL, FALSE) == FALSE) return FALSE; /* find out what type of authentication the token endpoint supports */ if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(cfg), NULL, TRUE) != NULL) { oidc_error(r, "could not find a supported token endpoint authentication method in provider metadata (%s) for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"", issuer); return FALSE; } return TRUE; } /* * check to see if dynamically registered JSON client metadata is valid and has not expired */ static apr_byte_t oidc_metadata_client_is_valid(request_rec *r, json_t *j_client, const char *issuer) { char *str; /* get a handle to the client_id we need to use for this provider */ str = NULL; oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID, &str, NULL); if (str == NULL) { oidc_error(r, "client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_ID "\" string", issuer); return FALSE; } /* get a handle to the client_secret we need to use for this provider */ str = NULL; oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET, &str, NULL); if (str == NULL) { oidc_warn(r, "client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET "\" string", issuer); } /* the expiry timestamp from the JSON object */ json_t *expires_at = json_object_get(j_client, OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT); if ((expires_at == NULL) || (!json_is_integer(expires_at))) { oidc_debug(r, "client (%s) metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "\" setting", issuer); /* assume that it never expires */ return TRUE; } /* see if it is unrestricted */ if (json_integer_value(expires_at) == 0) { oidc_debug(r, "client (%s) metadata never expires (" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "=0)", issuer); return TRUE; } /* check if the value >= now */ if (apr_time_sec(apr_time_now()) > json_integer_value(expires_at)) { oidc_warn(r, "client (%s) secret expired", issuer); return FALSE; } oidc_debug(r, "client (%s) metadata is valid", issuer); return TRUE; } /* * checks if a parsed JWKs file is a valid one, cq. contains "keys" */ static apr_byte_t oidc_metadata_jwks_is_valid(request_rec *r, const oidc_jwks_uri_t *jwks_uri, json_t *j_jwks) { json_t *keys = json_object_get(j_jwks, OIDC_METADATA_KEYS); if ((keys == NULL) || (!json_is_array(keys))) { oidc_error(r, "JWKs JSON metadata obtained from URL \"%s\" did not contain a \"" OIDC_METADATA_KEYS "\" array", jwks_uri->url); return FALSE; } return TRUE; } /* * check is a specified JOSE feature is supported */ static apr_byte_t oidc_metadata_conf_jose_is_supported(request_rec *r, json_t *j_conf, const char *issuer, const char *key, oidc_valid_function_t valid_function) { char *s_value = NULL; oidc_json_object_get_string(r->pool, j_conf, key, &s_value, NULL); if (s_value == NULL) return TRUE; const char *rv = valid_function(r->pool, s_value); if (rv != NULL) { oidc_error(r, "(%s) JSON conf data has \"%s\" entry but it contains an unsupported algorithm or encryption type: \"%s\" (%s)", issuer, key, s_value, rv); return FALSE; } return TRUE; } /* * check to see if JSON configuration data is valid */ static apr_byte_t oidc_metadata_conf_is_valid(request_rec *r, json_t *j_conf, const char *issuer) { if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg) == FALSE) return FALSE; if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc) == FALSE) return FALSE; return TRUE; } /* * register the client with the OP using Dynamic Client Registration */ static apr_byte_t oidc_metadata_client_register(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, json_t **j_client, char **response) { /* assemble the JSON registration request */ json_t *data = json_object(); json_object_set_new(data, OIDC_METADATA_CLIENT_NAME, json_string(provider->client_name)); json_object_set_new(data, OIDC_METADATA_REDIRECT_URIS, json_pack("[s]", oidc_get_redirect_uri_iss(r, cfg, provider))); json_t *response_types = json_array(); apr_array_header_t *flows = oidc_proto_supported_flows(r->pool); int i; for (i = 0; i < flows->nelts; i++) { json_array_append_new(response_types, json_string(((const char**) flows->elts)[i])); } json_object_set_new(data, OIDC_METADATA_RESPONSE_TYPES, response_types); if (provider->token_endpoint_auth != NULL) { json_object_set_new(data, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, json_string(provider->token_endpoint_auth)); } if (provider->client_contact != NULL) { json_object_set_new(data, OIDC_METADATA_CONTACTS, json_pack("[s]", provider->client_contact)); } if (provider->client_jwks_uri) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string(provider->client_jwks_uri)); } else if (cfg->public_keys != NULL) { json_object_set_new(data, OIDC_METADATA_JWKS_URI, json_string( apr_psprintf(r->pool, "%s?%s=rsa", oidc_get_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_JWKS))); } if (provider->id_token_signed_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, json_string(provider->id_token_signed_response_alg)); } if (provider->id_token_encrypted_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, json_string(provider->id_token_encrypted_response_alg)); } if (provider->id_token_encrypted_response_enc != NULL) { json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, json_string(provider->id_token_encrypted_response_enc)); } if (provider->userinfo_signed_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, json_string(provider->userinfo_signed_response_alg)); } if (provider->userinfo_encrypted_response_alg != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, json_string(provider->userinfo_encrypted_response_alg)); } if (provider->userinfo_encrypted_response_enc != NULL) { json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, json_string(provider->userinfo_encrypted_response_enc)); } json_object_set_new(data, OIDC_METADATA_INITIATE_LOGIN_URI, json_string(oidc_get_redirect_uri(r, cfg))); json_object_set_new(data, OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI, json_string( apr_psprintf(r->pool, "%s?%s=%s", oidc_get_redirect_uri(r, cfg), OIDC_REDIRECT_URI_REQUEST_LOGOUT, OIDC_GET_STYLE_LOGOUT_PARAM_VALUE))); if (cfg->default_slo_url != NULL) { json_object_set_new(data, OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS, json_pack("[s]", cfg->default_slo_url)); } /* add any custom JSON in to the registration request */ if (provider->registration_endpoint_json != NULL) { json_t *json = NULL; if (oidc_util_decode_json_object(r, provider->registration_endpoint_json, &json) == FALSE) return FALSE; oidc_util_json_merge(r, json, data); json_decref(json); } /* dynamically register the client with the specified parameters */ if (oidc_util_http_post_json(r, provider->registration_endpoint_url, data, NULL, provider->registration_token, provider->ssl_validate_server, response, cfg->http_timeout_short, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) { json_decref(data); return FALSE; } json_decref(data); /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, *response, j_client) == FALSE) { oidc_error(r, "JSON parsing of dynamic client registration response failed"); return FALSE; } return TRUE; } /* * helper function to get the JWKs for the specified issuer */ static apr_byte_t oidc_metadata_jwks_retrieve_and_cache(request_rec *r, oidc_cfg *cfg, const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks) { char *response = NULL; /* no valid provider metadata, get it at the specified URL with the specified parameters */ if (oidc_util_http_get(r, jwks_uri->url, NULL, NULL, NULL, jwks_uri->ssl_validate_server, &response, cfg->http_timeout_long, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) return FALSE; /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, response, j_jwks) == FALSE) { oidc_error(r, "JSON parsing of JWKs published at the jwks_uri failed"); return FALSE; } /* check to see if it is valid metadata */ if (oidc_metadata_jwks_is_valid(r, jwks_uri, *j_jwks) == FALSE) return FALSE; /* store the JWKs in the cache */ oidc_cache_set_jwks(r, oidc_metadata_jwks_cache_key(r, jwks_uri->url), response, apr_time_now() + apr_time_from_sec(jwks_uri->refresh_interval)); return TRUE; } /* * return JWKs for the specified issuer */ apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *cfg, const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks, apr_byte_t *refresh) { oidc_debug(r, "enter, jwks_uri=%s, refresh=%d", jwks_uri->url, *refresh); /* see if we need to do a forced refresh */ if (*refresh == TRUE) { oidc_debug(r, "doing a forced refresh of the JWKs from URI \"%s\"", jwks_uri->url); if (oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, j_jwks) == TRUE) return TRUE; // else: fallback on any cached JWKs } /* see if the JWKs is cached */ char *value = NULL; oidc_cache_get_jwks(r, oidc_metadata_jwks_cache_key(r, jwks_uri->url), &value); if (value == NULL) { /* it is non-existing or expired: do a forced refresh */ *refresh = TRUE; return oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, j_jwks); } /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, value, j_jwks) == FALSE) { oidc_error(r, "JSON parsing of cached JWKs data failed"); return FALSE; } return TRUE; } /* * use OpenID Connect Discovery to get metadata for the specified issuer */ apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response) { /* get provider metadata from the specified URL with the specified parameters */ if (oidc_util_http_get(r, url, NULL, NULL, NULL, cfg->provider.ssl_validate_server, response, cfg->http_timeout_short, cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) return FALSE; /* decode and see if it is not an error response somehow */ if (oidc_util_decode_json_and_check_error(r, *response, j_metadata) == FALSE) { oidc_error(r, "JSON parsing of retrieved Discovery document failed"); return FALSE; } /* check to see if it is valid metadata */ if (oidc_metadata_provider_is_valid(r, cfg, *j_metadata, issuer) == FALSE) return FALSE; /* all OK */ return TRUE; } /* * see if we have provider metadata and check its validity * if not, use OpenID Connect Discovery to get it, check it and store it */ static apr_byte_t oidc_metadata_provider_get(request_rec *r, oidc_cfg *cfg, const char *issuer, json_t **j_provider, apr_byte_t allow_discovery) { /* holds the response data/string/JSON from the OP */ char *response = NULL; /* get the full file path to the provider metadata for this issuer */ const char *provider_path = oidc_metadata_provider_file_path(r, issuer); /* check the last-modified timestamp */ apr_byte_t use_cache = TRUE; apr_finfo_t fi; json_t *j_cache = NULL; apr_byte_t have_cache = FALSE; /* see if we are refreshing metadata and we need a refresh */ if (cfg->provider_metadata_refresh_interval > 0) { have_cache = (apr_stat(&fi, provider_path, APR_FINFO_MTIME, r->pool) == APR_SUCCESS); if (have_cache == TRUE) use_cache = (apr_time_now() < fi.mtime + apr_time_from_sec( cfg->provider_metadata_refresh_interval)); oidc_debug(r, "use_cache: %s", use_cache ? "yes" : "no"); } /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, provider_path, &j_cache) == TRUE) { /* return the validation result */ if (use_cache == TRUE) { *j_provider = j_cache; return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer); } } if ((have_cache == FALSE) && (!allow_discovery)) { oidc_warn(r, "no metadata found for the requested issuer (%s), and Discovery is not allowed", issuer); return FALSE; } /* assemble the URL to the .well-known OpenID metadata */ const char *url = apr_psprintf(r->pool, "%s", ((strstr(issuer, "http://") == issuer) || (strstr(issuer, "https://") == issuer)) ? issuer : apr_psprintf(r->pool, "https://%s", issuer)); url = apr_psprintf(r->pool, "%s%s.well-known/openid-configuration", url, url[strlen(url) - 1] != OIDC_CHAR_FORWARD_SLASH ? OIDC_STR_FORWARD_SLASH : ""); /* get the metadata for the issuer using OpenID Connect Discovery and validate it */ if (oidc_metadata_provider_retrieve(r, cfg, issuer, url, j_provider, &response) == FALSE) { oidc_debug(r, "could not retrieve provider metadata; have_cache: %s (data=%pp)", have_cache ? "yes" : "no", j_cache); /* see if we can use at least the cache that may have expired by now */ if ((cfg->provider_metadata_refresh_interval > 0) && (have_cache == TRUE) && (j_cache != NULL)) { /* reset the file-modified timestamp so it is cached for a while again */ apr_file_mtime_set(provider_path, apr_time_now(), r->pool); /* return the validated cached data */ *j_provider = j_cache; return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer); } return FALSE; } /* since it is valid, write the obtained provider metadata file */ if (oidc_util_file_write(r, provider_path, response) == FALSE) return FALSE; return TRUE; } /* * see if we have config metadata */ static apr_byte_t oidc_metadata_conf_get(request_rec *r, oidc_cfg *cfg, const char *issuer, json_t **j_conf) { /* get the full file path to the conf metadata for this issuer */ const char *conf_path = oidc_metadata_conf_path(r, issuer); /* the .conf file is optional */ apr_finfo_t fi; if (apr_stat(&fi, conf_path, APR_FINFO_MTIME, r->pool) != APR_SUCCESS) return TRUE; /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, conf_path, j_conf) == TRUE) { /* return the validation result */ return oidc_metadata_conf_is_valid(r, *j_conf, issuer); } return FALSE; } /* * see if we have client metadata and check its validity * if not, use OpenID Connect Client Registration to get it, check it and store it */ static apr_byte_t oidc_metadata_client_get(request_rec *r, oidc_cfg *cfg, const char *issuer, oidc_provider_t *provider, json_t **j_client) { /* get the full file path to the client metadata for this issuer */ const char *client_path = oidc_metadata_client_file_path(r, issuer); /* see if we have valid metadata already, if so, return it */ if (oidc_metadata_file_read_json(r, client_path, j_client) == TRUE) { /* if the client metadata is (still) valid, return it */ if (oidc_metadata_client_is_valid(r, *j_client, issuer) == TRUE) return TRUE; } /* at this point we have no valid client metadata, see if there's a registration endpoint for this provider */ if (provider->registration_endpoint_url == NULL) { oidc_error(r, "no (valid) client metadata exists for provider (%s) and provider JSON object did not contain a (valid) \"" OIDC_METADATA_REGISTRATION_ENDPOINT "\" string", issuer); return FALSE; } /* try and get client metadata by registering the client at the registration endpoint */ char *response = NULL; if (oidc_metadata_client_register(r, cfg, provider, j_client, &response) == FALSE) return FALSE; /* check to see if it is valid metadata */ if (oidc_metadata_client_is_valid(r, *j_client, issuer) == FALSE) return FALSE; /* since it is valid, write the obtained client metadata file */ if (oidc_util_file_write(r, client_path, response) == FALSE) return FALSE; return TRUE; } /* * get a list of configured OIDC providers based on the entries in the provider metadata directory */ apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg, apr_array_header_t **list) { apr_status_t rc; apr_dir_t *dir; apr_finfo_t fi; char s_err[128]; oidc_debug(r, "enter"); /* open the metadata directory */ if ((rc = apr_dir_open(&dir, cfg->metadata_dir, r->pool)) != APR_SUCCESS) { oidc_error(r, "error opening metadata directory '%s' (%s)", cfg->metadata_dir, apr_strerror(rc, s_err, sizeof(s_err))); return FALSE; } /* allocate some space in the array that will hold the list of providers */ *list = apr_array_make(r->pool, 5, sizeof(const char*)); /* BTW: we could estimate the number in the array based on # directory entries... */ /* loop over the entries in the provider metadata directory */ while (apr_dir_read(&fi, APR_FINFO_NAME, dir) == APR_SUCCESS) { /* skip "." and ".." entries */ if (fi.name[0] == OIDC_CHAR_DOT) continue; /* skip other non-provider entries */ char *ext = strrchr(fi.name, OIDC_CHAR_DOT); if ((ext == NULL) || (strcmp(++ext, OIDC_METADATA_SUFFIX_PROVIDER) != 0)) continue; /* get the issuer from the filename */ const char *issuer = oidc_metadata_filename_to_issuer(r, fi.name); /* get the provider and client metadata, do all checks and registration if possible */ oidc_provider_t *provider = NULL; if (oidc_metadata_get(r, cfg, issuer, &provider, FALSE) == TRUE) { /* push the decoded issuer filename in to the array */ *(const char**) apr_array_push(*list) = provider->issuer; } } /* we're done, cleanup now */ apr_dir_close(dir); return TRUE; } /* * parse boolean value from JSON configuration */ static void oidc_metadata_parse_boolean(request_rec *r, json_t *json, const char *key, int *value, int default_value) { int int_value = 0; char *s_value = NULL; if (oidc_json_object_get_bool(r->pool, json, key, &int_value, default_value) == FALSE) { oidc_json_object_get_string(r->pool, json, key, &s_value, NULL); if (s_value != NULL) { const char *rv = oidc_parse_boolean(r->pool, s_value, &int_value); if (rv != NULL) { oidc_warn(r, "%s: %s", key, rv); int_value = default_value; } } else { oidc_json_object_get_int(r->pool, json, key, &int_value, default_value); } } *value = (int_value != 0) ? TRUE : FALSE; } /* * parse URL value from JSON configuration */ static void oidc_metadata_parse_url(request_rec *r, const char *type, const char *issuer, json_t *json, const char *key, char **value, const char *default_value) { if ((oidc_metadata_is_valid_uri(r, type, issuer, json, key, value, FALSE) == FALSE) || ((*value == NULL) && (default_value != NULL))) { *value = apr_pstrdup(r->pool, default_value); } } /* * parse the JSON provider metadata in to a oidc_provider_t struct but do not override values already set */ apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg *cfg, json_t *j_provider, oidc_provider_t *provider) { if (provider->issuer == NULL) { /* get the "issuer" from the provider metadata */ oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER, &provider->issuer, NULL); } if (provider->authorization_endpoint_url == NULL) { /* get a handle to the authorization endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_AUTHORIZATION_ENDPOINT, &provider->authorization_endpoint_url, NULL); } if (provider->token_endpoint_url == NULL) { /* get a handle to the token endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_TOKEN_ENDPOINT, &provider->token_endpoint_url, NULL); } if (provider->userinfo_endpoint_url == NULL) { /* get a handle to the user_info endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_USERINFO_ENDPOINT, &provider->userinfo_endpoint_url, NULL); } if (provider->jwks_uri == NULL) { /* get a handle to the jwks_uri endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_JWKS_URI, &provider->jwks_uri, NULL); } if (provider->registration_endpoint_url == NULL) { /* get a handle to the client registration endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_REGISTRATION_ENDPOINT, &provider->registration_endpoint_url, NULL); } if (provider->check_session_iframe == NULL) { /* get a handle to the check session iframe */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_CHECK_SESSION_IFRAME, &provider->check_session_iframe, NULL); } if (provider->end_session_endpoint == NULL) { /* get a handle to the end session endpoint */ oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, provider->issuer, j_provider, OIDC_METADATA_END_SESSION_ENDPOINT, &provider->end_session_endpoint, NULL); } if (provider->token_endpoint_auth == NULL) { if (oidc_valid_string_in_array(r->pool, j_provider, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, oidc_cfg_get_valid_endpoint_auth_function(cfg), &provider->token_endpoint_auth, TRUE) != NULL) { oidc_error(r, "could not find a supported token endpoint authentication method in provider metadata (%s) for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"", provider->issuer); return FALSE; } } return TRUE; } /* * get a string value from a JSON object and see if it is a valid value according to the specified validation function */ void oidc_metadata_get_valid_string(request_rec *r, json_t *json, const char *key, oidc_valid_function_t valid_function, char **str_value, const char *default_str_value) { char *v = NULL; oidc_json_object_get_string(r->pool, json, key, &v, default_str_value); if (v != NULL) { const char *rv = valid_function(r->pool, v); if (rv != NULL) { oidc_warn(r, "string value %s for key \"%s\" is invalid: %s; using default: %s", v, key, rv, default_str_value); v = apr_pstrdup(r->pool, default_str_value); } } *str_value = v; } /* * get an integer value from a JSON object and see if it is a valid value according to the specified validation function */ void oidc_metadata_get_valid_int(request_rec *r, json_t *json, const char *key, oidc_valid_int_function_t valid_int_function, int *int_value, int default_int_value) { int v = 0; oidc_json_object_get_int(r->pool, json, key, &v, default_int_value); const char *rv = valid_int_function(r->pool, v); if (rv != NULL) { oidc_warn(r, "integer value %d for key \"%s\" is invalid: %s; using default: %d", v, key, rv, default_int_value); v = default_int_value; } *int_value = v; } /* * parse the JSON conf metadata in to a oidc_provider_t struct */ apr_byte_t oidc_metadata_conf_parse(request_rec *r, oidc_cfg *cfg, json_t *j_conf, oidc_provider_t *provider) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_CONF, provider->issuer, j_conf, OIDC_METADATA_CLIENT_JWKS_URI, &provider->client_jwks_uri, cfg->provider.client_jwks_uri); /* get the (optional) signing & encryption settings for the id_token */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg, &provider->id_token_signed_response_alg, cfg->provider.id_token_signed_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg, &provider->id_token_encrypted_response_alg, cfg->provider.id_token_encrypted_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc, &provider->id_token_encrypted_response_enc, cfg->provider.id_token_encrypted_response_enc); /* get the (optional) signing & encryption settings for the userinfo response */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG, oidc_valid_signed_response_alg, &provider->userinfo_signed_response_alg, cfg->provider.userinfo_signed_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG, oidc_valid_encrypted_response_alg, &provider->userinfo_encrypted_response_alg, cfg->provider.userinfo_encrypted_response_alg); oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC, oidc_valid_encrypted_response_enc, &provider->userinfo_encrypted_response_enc, cfg->provider.userinfo_encrypted_response_enc); /* find out if we need to perform SSL server certificate validation on the token_endpoint and user_info_endpoint for this provider */ oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_SSL_VALIDATE_SERVER, &provider->ssl_validate_server, cfg->provider.ssl_validate_server); /* find out what scopes we should be requesting from this provider */ // TODO: use the provider "scopes_supported" to mix-and-match with what we've configured for the client // TODO: check that "openid" is always included in the configured scopes, right? oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_SCOPE, &provider->scope, cfg->provider.scope); /* see if we've got a custom JWKs refresh interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_JWKS_REFRESH_INTERVAL, oidc_valid_jwks_refresh_interval, &provider->jwks_refresh_interval, cfg->provider.jwks_refresh_interval); /* see if we've got a custom IAT slack interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_IDTOKEN_IAT_SLACK, oidc_valid_idtoken_iat_slack, &provider->idtoken_iat_slack, cfg->provider.idtoken_iat_slack); /* see if we've got a custom max session duration */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_SESSION_MAX_DURATION, oidc_valid_session_max_duration, &provider->session_max_duration, cfg->provider.session_max_duration); /* see if we've got custom authentication request parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_AUTH_REQUEST_PARAMS, &provider->auth_request_params, cfg->provider.auth_request_params); /* see if we've got custom token endpoint parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_PARAMS, &provider->token_endpoint_params, cfg->provider.token_endpoint_params); /* get the response mode to use */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_MODE, oidc_valid_response_mode, &provider->response_mode, cfg->provider.response_mode); /* get the PKCE method to use */ char *pkce_method = NULL; oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_PKCE_METHOD, oidc_valid_pkce_method, &pkce_method, cfg->provider.pkce ? cfg->provider.pkce->method : NULL); if (pkce_method != NULL) oidc_parse_pkce_type(r->pool, pkce_method, &provider->pkce); /* get the client name */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_NAME, &provider->client_name, cfg->provider.client_name); /* get the client contact */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_CONTACT, &provider->client_contact, cfg->provider.client_contact); /* get the token endpoint authentication method */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_AUTH, oidc_cfg_get_valid_endpoint_auth_function(cfg), &provider->token_endpoint_auth, provider->token_endpoint_auth); /* get the dynamic client registration token */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_TOKEN, &provider->registration_token, cfg->provider.registration_token); /* see if we've got custom registration request parameter values */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REGISTRATION_ENDPOINT_JSON, &provider->registration_endpoint_json, cfg->provider.registration_endpoint_json); /* get the flow to use; let the .client file set it otherwise (pass NULL as default value) */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_TYPE, oidc_valid_response_type, &provider->response_type, NULL); /* see if we've got a custom user info refresh interval */ oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_USERINFO_REFRESH_INTERVAL, oidc_valid_userinfo_refresh_interval, &provider->userinfo_refresh_interval, cfg->provider.userinfo_refresh_interval); /* TLS client cert auth settings */ oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT, &provider->token_endpoint_tls_client_cert, cfg->provider.token_endpoint_tls_client_cert); oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY, &provider->token_endpoint_tls_client_key, cfg->provider.token_endpoint_tls_client_key); oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REQUEST_OBJECT, &provider->request_object, cfg->provider.request_object); /* see if we've got a custom userinfo endpoint token presentation method */ char *method = NULL; oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_USERINFO_TOKEN_METHOD, oidc_valid_userinfo_token_method, &method, NULL); if (method != NULL) oidc_parse_userinfo_token_method(r->pool, method, &provider->userinfo_token_method); else provider->userinfo_token_method = OIDC_USER_INFO_TOKEN_METHOD_HEADER; /* see if we've got a custom token binding policy */ char *policy = NULL; oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_TOKEN_BINDING_POLICY, oidc_valid_token_binding_policy, &policy, NULL); if (policy != NULL) oidc_parse_token_binding_policy(r->pool, policy, &provider->token_binding_policy); else provider->token_binding_policy = cfg->provider.token_binding_policy; /* see if we've got a custom HTTP method for passing the auth request */ oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_AUTH_REQUEST_METHOD, oidc_valid_auth_request_method, &method, NULL); if (method != NULL) oidc_parse_auth_request_method(r->pool, method, &provider->auth_request_method); else provider->auth_request_method = cfg->provider.auth_request_method; /* get the issuer specific redirect URI option */ oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_ISSUER_SPECIFIC_REDIRECT_URI, &provider->issuer_specific_redirect_uri, cfg->provider.issuer_specific_redirect_uri); return TRUE; } /* * parse the JSON client metadata in to a oidc_provider_t struct */ apr_byte_t oidc_metadata_client_parse(request_rec *r, oidc_cfg *cfg, json_t *j_client, oidc_provider_t *provider) { /* get a handle to the client_id we need to use for this provider */ oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID, &provider->client_id, NULL); /* get a handle to the client_secret we need to use for this provider */ oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET, &provider->client_secret, NULL); /* see if the token endpoint auth method defined in the client metadata overrides the provider one */ char *token_endpoint_auth = NULL; oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, &token_endpoint_auth, NULL); if (token_endpoint_auth != NULL) { if ((apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_POST) == 0) || (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_BASIC) == 0) || (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_JWT) == 0) || (apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_PRIVATE_KEY_JWT) == 0)) { provider->token_endpoint_auth = apr_pstrdup(r->pool, token_endpoint_auth); } else { oidc_warn(r, "unsupported client auth method \"%s\" in client metadata for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "\"", token_endpoint_auth); } } /* determine the response type if not set by .conf */ if (provider->response_type == NULL) { provider->response_type = cfg->provider.response_type; /* "response_types" is an array in the client metadata as by spec */ json_t *j_response_types = json_object_get(j_client, OIDC_METADATA_RESPONSE_TYPES); if ((j_response_types != NULL) && (json_is_array(j_response_types))) { /* if there's an array we'll prefer the configured response_type if supported */ if (oidc_util_json_array_has_value(r, j_response_types, provider->response_type) == FALSE) { /* if the configured response_type is not supported, we'll fallback to the first one that is listed */ json_t *j_response_type = json_array_get(j_response_types, 0); if (json_is_string(j_response_type)) { provider->response_type = apr_pstrdup(r->pool, json_string_value(j_response_type)); } } } } return TRUE; } /* * get the metadata for a specified issuer * * this fill the oidc_provider_t struct based on the issuer filename by reading and merging * contents from both provider metadata directory and client metadata directory */ apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg *cfg, const char *issuer, oidc_provider_t **provider, apr_byte_t allow_discovery) { apr_byte_t rc = FALSE; /* pointers to the parsed JSON metadata */ json_t *j_provider = NULL; json_t *j_client = NULL; json_t *j_conf = NULL; /* allocate space for a parsed-and-merged metadata struct */ *provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t)); /* * read and parse the provider, conf and client metadata respectively * NB: order is important here */ if (oidc_metadata_provider_get(r, cfg, issuer, &j_provider, allow_discovery) == FALSE) goto end; if (oidc_metadata_provider_parse(r, cfg, j_provider, *provider) == FALSE) goto end; if (oidc_metadata_conf_get(r, cfg, issuer, &j_conf) == FALSE) goto end; if (oidc_metadata_conf_parse(r, cfg, j_conf, *provider) == FALSE) goto end; if (oidc_metadata_client_get(r, cfg, issuer, *provider, &j_client) == FALSE) goto end; if (oidc_metadata_client_parse(r, cfg, j_client, *provider) == FALSE) goto end; rc = TRUE; end: if (j_provider) json_decref(j_provider); if (j_conf) json_decref(j_conf); if (j_client) json_decref(j_client); return rc; } mod_auth_openidc-2.3.3/src/jose.c0000644000076500000240000010742013200665522016556 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JSON Web Token handling * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include "jose.h" #include #include #include #include #include #include #ifdef WIN32 #define snprintf _snprintf #endif /* * assemble an error report */ void _oidc_jose_error_set(oidc_jose_error_t *error, const char *source, const int line, const char *function, const char *msg, ...) { if (error == NULL) return; snprintf(error->source, OIDC_JOSE_ERROR_SOURCE_LENGTH, "%s", source); error->line = line; snprintf(error->function, OIDC_JOSE_ERROR_FUNCTION_LENGTH, "%s", function); va_list ap; va_start(ap, msg); vsnprintf(error->text, OIDC_JOSE_ERROR_TEXT_LENGTH, msg, ap); va_end(ap); } /* * set a header value in a JWT */ static void oidc_jwt_hdr_set(oidc_jwt_t *jwt, const char *key, const char *value) { json_object_set_new(jwt->header.value.json, key, json_string(value)); } /* * create a new JWT */ oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload) { oidc_jwt_t *jwt = apr_pcalloc(pool, sizeof(oidc_jwt_t)); if (create_header) { jwt->header.value.json = json_object(); //oidc_jwt_hdr_set(jwt, "typ", "JWT"); } if (create_payload) { jwt->payload.value.json = json_object(); } return jwt; } /* * get a header value from a JWT */ const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key) { cjose_err cjose_err; cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); return hdr ? cjose_header_get(hdr, key, &cjose_err) : NULL; } /* * {"alg":"none"} */ #define OIDC_JOSE_HDR_ALG_NONE "eyJhbGciOiJub25lIn0" /* * perform compact serialization on a JWT and return the resulting string */ char *oidc_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) { cjose_err cjose_err; const char *cser = NULL; if (strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) != 0) { if (cjose_jws_export(jwt->cjose_jws, &cser, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } } else { char *s_payload = json_dumps(jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); char *out = NULL; size_t out_len; if (cjose_base64url_encode((const uint8_t *) s_payload, strlen(s_payload), &out, &out_len, &cjose_err) == FALSE) return FALSE; cser = apr_pstrmemdup(pool, out, out_len); cjose_get_dealloc()(out); free(s_payload); cser = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, cser); } return apr_pstrdup(pool, cser); } /* * return the key type for an algorithm */ static int oidc_alg2kty(const char *alg) { if (strcmp(alg, CJOSE_HDR_ALG_DIR) == 0) return CJOSE_JWK_KTY_OCT; if (strncmp(alg, "RS", 2) == 0) return CJOSE_JWK_KTY_RSA; if (strncmp(alg, "PS", 2) == 0) return CJOSE_JWK_KTY_RSA; if (strncmp(alg, "HS", 2) == 0) return CJOSE_JWK_KTY_OCT; #if (OIDC_JOSE_EC_SUPPORT) if (strncmp(alg, "ES", 2) == 0) return CJOSE_JWK_KTY_EC; #endif if ((strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) || (strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) || (strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)) return CJOSE_JWK_KTY_OCT; if ((strcmp(alg, CJOSE_HDR_ALG_RSA1_5) == 0) || (strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP) == 0)) return CJOSE_JWK_KTY_RSA; return -1; } /* * return the key type of a JWT */ int oidc_jwt_alg2kty(oidc_jwt_t *jwt) { return oidc_alg2kty(jwt->header.alg); } /* * return the key size for an algorithm */ unsigned int oidc_alg2keysize(const char *alg) { if (alg == NULL) return 0; if (strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) return 16; if (strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) return 24; if (strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0) return 32; if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0)) return 32; if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0)) return 48; if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)) return 64; return 0; } /* * create a new JWK */ static oidc_jwk_t *oidc_jwk_new(apr_pool_t *pool) { oidc_jwk_t *jwk = apr_pcalloc(pool, sizeof(oidc_jwk_t)); return jwk; } static apr_byte_t oidc_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err); #define OIDC_JOSE_HDR_KTY "kty" #define OIDC_JOSE_HDR_KTY_RSA "RSA" #define OIDC_JOSE_HDR_X5C "x5c" /* * parse a JSON object with an RSA "x5c" JWK representation in to a cjose JWK object */ static cjose_jwk_t *oidc_jwk_parse_rsa_x5c_spec(apr_pool_t *pool, const char *s_json, oidc_jose_error_t *err) { cjose_jwk_t *cjose_jwk = NULL; json_error_t json_error; json_t *json = json_loads(s_json, 0, &json_error); if (json == NULL) { oidc_jose_error(err, "could not parse JWK: %s (%s)", json_error.text, s_json); goto end; } char *kty = NULL; oidc_jose_get_string(pool, json, OIDC_JOSE_HDR_KTY, FALSE, &kty, NULL); if (kty == NULL) { oidc_jose_error(err, "no key type \"" OIDC_JOSE_HDR_KTY "\" found in JWK JSON value"); goto end; } if (apr_strnatcmp(kty, OIDC_JOSE_HDR_KTY_RSA) != 0) { oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_KTY_RSA "\" key type found JWK JSON value"); goto end; } json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); if (v == NULL) { oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_X5C "\" key found in JWK JSON value"); goto end; } oidc_jwk_parse_rsa_x5c(pool, json, &cjose_jwk, err); end: if (json) json_decref(json); return cjose_jwk; } /* * create a JWK struct from a cjose_jwk object */ static oidc_jwk_t *oidc_jwk_from_cjose(apr_pool_t *pool, cjose_jwk_t *cjose_jwk) { cjose_err cjose_err; oidc_jwk_t *jwk = oidc_jwk_new(pool); jwk->cjose_jwk = cjose_jwk; jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); return jwk; } /* * parse a JSON string to a JWK struct */ oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const char *s_json, oidc_jose_error_t *err) { cjose_err cjose_err; cjose_jwk_t *cjose_jwk = cjose_jwk_import(s_json, strlen(s_json), &cjose_err); if (cjose_jwk == NULL) { // exception because x5c is not supported by cjose natively // ignore errors set by oidc_jwk_parse_rsa_x5c_spec oidc_jose_error_t x5c_err; cjose_jwk = oidc_jwk_parse_rsa_x5c_spec(pool, s_json, &x5c_err); if (cjose_jwk == NULL) { oidc_jose_error(err, "JWK parsing failed: %s", oidc_cjose_e2s(pool, cjose_err)); return NULL; } } return oidc_jwk_from_cjose(pool, cjose_jwk); } /* * destroy resources allocated for a JWK struct */ void oidc_jwk_destroy(oidc_jwk_t *jwk) { if (jwk) { if (jwk->cjose_jwk) { cjose_jwk_release(jwk->cjose_jwk); jwk->cjose_jwk = NULL; } } } /* * destroy a list of JWKs structs */ void oidc_jwk_list_destroy(apr_pool_t *pool, apr_hash_t *keys) { apr_hash_index_t *hi = NULL; if (keys == NULL) return; for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { oidc_jwk_t *jwk = NULL; apr_hash_this(hi, NULL, NULL, (void **) &jwk); oidc_jwk_destroy(jwk); } } /* * parse a JSON object in to a JWK struct */ apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk, oidc_jose_error_t *err) { char *s_json = json_dumps(json, 0); *jwk = oidc_jwk_parse(pool, s_json, err); free(s_json); return (*jwk != NULL); } /* * convert a JWK struct to a JSON string */ apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *err) { cjose_err cjose_err; char *s = cjose_jwk_to_json(jwk->cjose_jwk, TRUE, &cjose_err); if (s == NULL) { oidc_jose_error(err, "cjose_jwk_to_json failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } *s_json = apr_pstrdup(pool, s); free(s); return TRUE; } /* * hash a sequence of bytes with a specific algorithm and return the result as a base64url-encoded \0 terminated string */ static apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *openssl_hash_algo, const char *input, int input_len, char **output) { oidc_jose_error_t err; unsigned char *hashed = NULL; unsigned int hashed_len = 0; if (oidc_jose_hash_bytes(pool, openssl_hash_algo, (const unsigned char *) input, input_len, &hashed, &hashed_len, &err) == FALSE) { return FALSE; } char *out = NULL; size_t out_len; cjose_err cjose_err; if (cjose_base64url_encode(hashed, hashed_len, &out, &out_len, &cjose_err) == FALSE) return FALSE; *output = apr_pstrmemdup(pool, out, out_len); cjose_get_dealloc()(out); return TRUE; } /* * set a specified key identifier or generate a key identifier and set it */ static apr_byte_t oidc_jwk_set_or_generate_kid(apr_pool_t *pool, cjose_jwk_t *cjose_jwk, const char *s_kid, const char *key_params, int key_params_len, oidc_jose_error_t *err) { char *jwk_kid = NULL; if (s_kid != NULL) { jwk_kid = apr_pstrdup(pool, s_kid); } else { /* calculate a unique key identifier (kid) by fingerprinting the key params */ if (oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, key_params, key_params_len, &jwk_kid) == FALSE) { oidc_jose_error(err, "oidc_jose_hash_and_base64urlencode failed"); return FALSE; } } cjose_err cjose_err; if (cjose_jwk_set_kid(cjose_jwk, jwk_kid, strlen(jwk_kid), &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jwk_set_kid failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } return TRUE; } /* * create an "oct" symmetric JWK */ oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *skid, const unsigned char *key, unsigned int key_len, apr_byte_t set_kid, oidc_jose_error_t *err) { cjose_err cjose_err; cjose_jwk_t *cjose_jwk = cjose_jwk_create_oct_spec(key, key_len, &cjose_err); if (cjose_jwk == NULL) { oidc_jose_error(err, "cjose_jwk_create_oct_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } if (set_kid == TRUE) { if (oidc_jwk_set_or_generate_kid(pool, cjose_jwk, skid, (const char *) key, key_len, err) == FALSE) { cjose_jwk_release(cjose_jwk); return FALSE; } } oidc_jwk_t *jwk = oidc_jwk_new(pool); jwk->cjose_jwk = cjose_jwk; jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); return jwk; } /* * check if a string is an element of an array of strings */ static apr_byte_t oidc_jose_array_has_string(apr_array_header_t *haystack, const char *needle) { int i = 0; while (i < haystack->nelts) { if (apr_strnatcmp(((const char**) haystack->elts)[i], needle) == 0) return TRUE; i++; } return FALSE; } /* * return all supported signing algorithms */ apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 12, sizeof(const char*)); *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS256; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS384; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS512; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS256; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS384; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS512; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS256; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS384; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS512; #if (OIDC_JOSE_EC_SUPPORT) *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES256; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES384; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES512; #endif *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_NONE; return result; } /* * check if the provided signing algorithm is supported */ apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg) { return oidc_jose_array_has_string(oidc_jose_jws_supported_algorithms(pool), alg); } /* * return all supported content encryption key algorithms */ apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 4, sizeof(const char*)); *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RSA1_5; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A128KW; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A192KW; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A256KW; *(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RSA_OAEP; return result; } /* * check if the provided content encryption key algorithm is supported */ apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg) { return oidc_jose_array_has_string(oidc_jose_jwe_supported_algorithms(pool), alg); } /* * return all supported encryption algorithms */ apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool) { apr_array_header_t *result = apr_array_make(pool, 5, sizeof(const char*)); *(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A128CBC_HS256; *(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A192CBC_HS384; *(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A256CBC_HS512; #if (OIDC_JOSE_GCM_SUPPORT) *(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A256GCM; #endif return result; } /* * check if the provided encryption algorithm is supported */ apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc) { return oidc_jose_array_has_string(oidc_jose_jwe_supported_encryptions(pool), enc); } /* * get (optional) string from JWT */ apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, char **result, oidc_jose_error_t *err) { json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_string(v)) { *result = apr_pstrdup(pool, json_string_value(v)); } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a string", claim_name); return FALSE; } } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; } /* * parse (optional) timestamp from payload */ static apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, double *result, oidc_jose_error_t *err) { *result = OIDC_JWT_CLAIM_TIME_EMPTY; json_t *v = json_object_get(json, claim_name); if (v != NULL) { if (json_is_number(v)) { *result = json_number_value(v); } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a number", claim_name); return FALSE; } } else if (is_mandatory) { oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); return FALSE; } return TRUE; } #define OIDC_JOSE_JWT_ISS "iss" #define OIDC_JOSE_JWT_SUB "sub" #define OIDC_JOSE_JWT_EXP "exp" #define OIDC_JOSE_JWT_IAT "iat" /* * parse JWT payload */ static apr_byte_t oidc_jose_parse_payload(apr_pool_t *pool, const char *s_payload, size_t s_payload_len, oidc_jwt_payload_t *payload, oidc_jose_error_t *err) { /* decode the string in to a JSON structure into value->json */ json_error_t json_error; payload->value.str = apr_pstrndup(pool, s_payload, s_payload_len); payload->value.json = json_loads(payload->value.str, 0, &json_error); /* check that we've actually got a JSON value back */ if (payload->value.json == NULL) { oidc_jose_error(err, "JSON parsing (json_loads) failed: %s (%s)", json_error.text, s_payload); return FALSE; } /* check that the value is a JSON object */ if (!json_is_object(payload->value.json)) { oidc_jose_error(err, "JSON value is not an object"); return FALSE; } /* get the (optional) "iss" value from the JSON payload */ oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_ISS, FALSE, &payload->iss, NULL); /* get the (optional) "exp" value from the JSON payload */ oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_EXP, FALSE, &payload->exp, NULL); /* get the (optional) "iat" value from the JSON payload */ oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_IAT, FALSE, &payload->iat, NULL); /* get the (optional) "sub" value from the JSON payload */ oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_SUB, FALSE, &payload->sub, NULL); return TRUE; } /* * decrypt a JWT and return the plaintext */ static uint8_t *oidc_jwe_decrypt_impl(apr_pool_t *pool, cjose_jwe_t *jwe, apr_hash_t *keys, size_t *content_len, oidc_jose_error_t *err) { uint8_t *decrypted = NULL; oidc_jwk_t *jwk = NULL; apr_hash_index_t *hi; cjose_err cjose_err; cjose_header_t *hdr = cjose_jwe_get_protected(jwe); const char *kid = cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err); const char *alg = cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err); if (kid != NULL) { jwk = apr_hash_get(keys, kid, APR_HASH_KEY_STRING); if (jwk != NULL) { decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len, &cjose_err); if (decrypted == NULL) oidc_jose_error(err, "encrypted JWT could not be decrypted with kid %s: %s", kid, oidc_cjose_e2s(pool, cjose_err)); } else { oidc_jose_error(err, "could not find key with kid: %s", kid); } } else { for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void **) &jwk); if (jwk->kty == oidc_alg2kty(alg)) { decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len, &cjose_err); if (decrypted != NULL) break; } } if (decrypted == NULL) oidc_jose_error(err, "encrypted JWT could not be decrypted with any of the %d keys: error for last tried key is: %s", apr_hash_count(keys), oidc_cjose_e2s(pool, cjose_err)); } return decrypted; } /* * decrypt a JSON Web Token */ apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json, apr_hash_t *keys, char **s_json, oidc_jose_error_t *err, apr_byte_t import_must_succeed) { cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(input_json, strlen(input_json), &cjose_err); if (jwe != NULL) { size_t content_len = 0; uint8_t *decrypted = oidc_jwe_decrypt_impl(pool, jwe, keys, &content_len, err); if (decrypted != NULL) { decrypted[content_len] = '\0'; *s_json = apr_pstrdup(pool, (const char *) decrypted); cjose_get_dealloc()(decrypted); } cjose_jwe_release(jwe); } else if (import_must_succeed == FALSE) { *s_json = apr_pstrdup(pool, input_json); } else { oidc_jose_error(err, "cjose_jwe_import failed: %s", oidc_cjose_e2s(pool, cjose_err)); } return (*s_json != NULL); } /* * parse and (optionally) decrypt a JSON Web Token */ apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *input_json, oidc_jwt_t **j_jwt, apr_hash_t *keys, oidc_jose_error_t *err) { cjose_err cjose_err; char *s_json = NULL; if (oidc_jwe_decrypt(pool, input_json, keys, &s_json, err, FALSE) == FALSE) return FALSE; *j_jwt = oidc_jwt_new(pool, FALSE, FALSE); if (*j_jwt == NULL) return FALSE; oidc_jwt_t *jwt = *j_jwt; jwt->cjose_jws = cjose_jws_import(s_json, strlen(s_json), &cjose_err); if (jwt->cjose_jws == NULL) { oidc_jose_error(err, "cjose_jws_import failed: %s", oidc_cjose_e2s(pool, cjose_err)); oidc_jwt_destroy(jwt); *j_jwt = NULL; return FALSE; } cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); jwt->header.value.json = json_deep_copy((json_t *) hdr); char *str = json_dumps(jwt->header.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); jwt->header.value.str = apr_pstrdup(pool, str); free(str); jwt->header.alg = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err)); jwt->header.enc = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ENC, &cjose_err)); jwt->header.kid = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err)); uint8_t *plaintext = NULL; size_t plaintext_len = 0; if (cjose_jws_get_plaintext(jwt->cjose_jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) { oidc_jose_error(err, "cjose_jws_get_plaintext failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } if (oidc_jose_parse_payload(pool, (const char *) plaintext, plaintext_len, &jwt->payload, err) == FALSE) { oidc_jwt_destroy(jwt); *j_jwt = NULL; } return TRUE; } /* destroy resources allocated for JWT */ void oidc_jwt_destroy(oidc_jwt_t *jwt) { if (jwt) { if (jwt->header.value.json) { json_decref(jwt->header.value.json); jwt->header.value.json = NULL; jwt->header.value.str = NULL; } if (jwt->payload.value.json) { json_decref(jwt->payload.value.json); jwt->payload.value.json = NULL; jwt->payload.value.str = NULL; } if (jwt->cjose_jws) { cjose_jws_release(jwt->cjose_jws); jwt->cjose_jws = NULL; } } } /* * sign JWT */ apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk, oidc_jose_error_t *err) { cjose_header_t *hdr = (cjose_header_t *) jwt->header.value.json; if (jwt->header.alg) oidc_jwt_hdr_set(jwt, CJOSE_HDR_ALG, jwt->header.alg); if (jwt->header.kid) oidc_jwt_hdr_set(jwt, CJOSE_HDR_KID, jwt->header.kid); if (jwt->header.enc) oidc_jwt_hdr_set(jwt, CJOSE_HDR_ENC, jwt->header.enc); if (jwt->cjose_jws) cjose_jws_release(jwt->cjose_jws); cjose_err cjose_err; char *s_payload = json_dumps(jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); jwt->payload.value.str = apr_pstrdup(pool, s_payload); jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr, (const uint8_t *) s_payload, strlen(s_payload), &cjose_err); free(s_payload); if (jwt->cjose_jws == NULL) { oidc_jose_error(err, "cjose_jws_sign failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } return TRUE; } #if (OPENSSL_VERSION_NUMBER < 0x10100000) EVP_MD_CTX * EVP_MD_CTX_new() { return malloc(sizeof(EVP_MD_CTX)); } void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { if (ctx) free(ctx); } #endif /* * encrypt JWT */ apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk, const char *payload, char **serialized, oidc_jose_error_t *err) { cjose_header_t *hdr = (cjose_header_t *) jwe->header.value.json; if (jwe->header.alg) oidc_jwt_hdr_set(jwe, CJOSE_HDR_ALG, jwe->header.alg); if (jwe->header.kid) oidc_jwt_hdr_set(jwe, CJOSE_HDR_KID, jwe->header.kid); if (jwe->header.enc) oidc_jwt_hdr_set(jwe, CJOSE_HDR_ENC, jwe->header.enc); cjose_err cjose_err; cjose_jwe_t *cjose_jwe = cjose_jwe_encrypt(jwk->cjose_jwk, hdr, (const uint8_t *) payload, strlen(payload), &cjose_err); if (cjose_jwe == NULL) { oidc_jose_error(err, "cjose_jwe_encrypt failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } char *cser = cjose_jwe_export(cjose_jwe, &cjose_err); if (cser == NULL) { oidc_jose_error(err, "cjose_jwe_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); return FALSE; } *serialized = apr_pstrdup(pool, cser); cjose_get_dealloc()(cser); cjose_jwe_release(cjose_jwe); return TRUE; } #define OIDC_JOSE_CJOSE_VERSION_DEPRECATED "0.4." /* * check for a version of cjose < 0.5.0 that has a version of * cjose_jws_verify that resources after a verification failure */ apr_byte_t oidc_jose_version_deprecated(apr_pool_t *pool) { char *version = apr_pstrdup(pool, cjose_version()); return (strstr(version, OIDC_JOSE_CJOSE_VERSION_DEPRECATED) == version); } /* * verify the signature on a JWT */ apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) { apr_byte_t rc = FALSE; oidc_jwk_t *jwk = NULL; apr_hash_index_t *hi; cjose_err cjose_err; if (jwt->header.kid != NULL) { jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING); if (jwk != NULL) { rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err); if (rc == FALSE) { oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err)); if (oidc_jose_version_deprecated(pool)) jwt->cjose_jws = NULL; } } else { oidc_jose_error(err, "could not find key with kid: %s", jwt->header.kid); rc = FALSE; } } else { for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void **) &jwk); if (jwk->kty == oidc_jwt_alg2kty(jwt)) { rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err); if (rc == FALSE) { oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err)); if (oidc_jose_version_deprecated(pool)) jwt->cjose_jws = NULL; } } if ((rc == TRUE) || (jwt->cjose_jws == NULL)) break; } if (rc == FALSE) oidc_jose_error(err, "could not verify signature against any of the (%d) provided keys%s", apr_hash_count(keys), apr_hash_count(keys) > 0 ? "" : apr_psprintf(pool, "; you have probably provided no or incorrect keys/key-types for algorithm: %s", jwt->header.alg)); } return rc; } /* * hash a byte sequence with the specified algorithm */ apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, unsigned int input_len, unsigned char **output, unsigned int *output_len, oidc_jose_error_t *err) { unsigned char md_value[EVP_MAX_MD_SIZE]; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_MD_CTX_init(ctx); const EVP_MD *evp_digest = NULL; if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) { oidc_jose_error(err, "no OpenSSL digest algorithm found for algorithm \"%s\"", s_digest); return FALSE; } if (!EVP_DigestInit_ex(ctx, evp_digest, NULL)) { oidc_jose_error_openssl(err, "EVP_DigestInit_ex"); return FALSE; } if (!EVP_DigestUpdate(ctx, input, input_len)) { oidc_jose_error_openssl(err, "EVP_DigestUpdate"); return FALSE; } if (!EVP_DigestFinal(ctx, md_value, output_len)) { oidc_jose_error_openssl(err, "EVP_DigestFinal"); return FALSE; } EVP_MD_CTX_free(ctx); *output = apr_pcalloc(pool, *output_len); memcpy(*output, md_value, *output_len); return TRUE; } /* * return the OpenSSL hash algorithm associated with a specified JWT algorithm */ static char *oidc_jose_alg_to_openssl_digest(const char *alg) { if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) { return LN_sha256; } if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) { return LN_sha384; } if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) { return LN_sha512; } return NULL; } /* * hash a string value with the specified algorithm */ apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, unsigned int *hash_len, oidc_jose_error_t *err) { char *s_digest = oidc_jose_alg_to_openssl_digest(alg); if (s_digest == NULL) { oidc_jose_error(err, "no OpenSSL digest algorithm name found for algorithm \"%s\"", alg); return FALSE; } return oidc_jose_hash_bytes(pool, s_digest, (const unsigned char *) msg, strlen(msg), (unsigned char **) hash, hash_len, err); } /* * return hash length */ int oidc_jose_hash_length(const char *alg) { if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) { return 32; } if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) { return 48; } if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0) || (strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) { return 64; } return 0; } /* * convert the RSA public key in the X.509 certificate in the BIO pointed to * by "input" to a JSON Web Key object */ static apr_byte_t oidc_jwk_rsa_bio_to_jwk(apr_pool_t *pool, BIO *input, const char *kid, cjose_jwk_t **jwk, int is_private_key, oidc_jose_error_t *err) { X509 *x509 = NULL; EVP_PKEY *pkey = NULL; apr_byte_t rv = FALSE; cjose_jwk_rsa_keyspec key_spec; memset(&key_spec, 0, sizeof(cjose_jwk_rsa_keyspec)); if (is_private_key) { /* get the private key struct from the BIO */ if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) { oidc_jose_error_openssl(err, "PEM_read_bio_PrivateKey"); goto end; } } else { /* read the X.509 struct */ if ((x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL)) == NULL) { oidc_jose_error_openssl(err, "PEM_read_bio_X509_AUX"); goto end; } /* get the public key struct from the X.509 struct */ if ((pkey = X509_get_pubkey(x509)) == NULL) { oidc_jose_error_openssl(err, "X509_get_pubkey"); goto end; } } /* get the RSA key from the public key struct */ RSA *rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { oidc_jose_error_openssl(err, "EVP_PKEY_get1_RSA"); goto end; } const BIGNUM *rsa_n, *rsa_e, *rsa_d; #if OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined (LIBRESSL_VERSION_NUMBER) RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d); #else rsa_n = rsa->n; rsa_e = rsa->e; rsa_d = rsa->d; #endif RSA_free(rsa); /* convert the modulus bignum in to a key/len */ key_spec.nlen = BN_num_bytes(rsa_n); key_spec.n = apr_pcalloc(pool, key_spec.nlen); BN_bn2bin(rsa_n, key_spec.n); /* convert the exponent bignum in to a key/len */ key_spec.elen = BN_num_bytes(rsa_e); key_spec.e = apr_pcalloc(pool, key_spec.elen); BN_bn2bin(rsa_e, key_spec.e); /* convert the private exponent bignum in to a key/len */ if (rsa_d != NULL) { key_spec.dlen = BN_num_bytes(rsa_d); key_spec.d = apr_pcalloc(pool, key_spec.dlen); BN_bn2bin(rsa_d, key_spec.d); } cjose_err cjose_err; *jwk = cjose_jwk_create_RSA_spec(&key_spec, &cjose_err); if (*jwk == NULL) { oidc_jose_error(err, "cjose_jwk_create_RSA_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); goto end; } char *fingerprint = apr_pcalloc(pool, key_spec.nlen + key_spec.elen); memcpy(fingerprint, key_spec.n, key_spec.nlen); memcpy(fingerprint + key_spec.nlen, key_spec.e, key_spec.elen); if (oidc_jwk_set_or_generate_kid(pool, *jwk, kid, fingerprint, key_spec.nlen + key_spec.elen, err) == FALSE) { goto end; } rv = TRUE; end: if (pkey) EVP_PKEY_free(pkey); if (x509) X509_free(x509); return rv; } /* * parse an RSA public or private key from the specified file */ static apr_byte_t oidc_jwk_parse_rsa_key(apr_pool_t *pool, int is_private_key, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { BIO *input = NULL; apr_byte_t rv = FALSE; if ((input = BIO_new(BIO_s_file())) == NULL) { oidc_jose_error_openssl(err, "BIO_new/BIO_s_file"); goto end; } if (BIO_read_filename(input, filename) <= 0) { oidc_jose_error_openssl(err, "BIO_read_filename"); goto end; } cjose_jwk_t *cjose_jwk = NULL; if (oidc_jwk_rsa_bio_to_jwk(pool, input, kid, &cjose_jwk, is_private_key, err) == FALSE) goto end; *jwk = oidc_jwk_from_cjose(pool, cjose_jwk); rv = TRUE; end: if (input) BIO_free(input); return rv; } #define OIDC_JOSE_CERT_BEGIN "-----BEGIN CERTIFICATE-----" #define OIDC_JOSE_CERT_END "-----END CERTIFICATE-----" /* * parse an RSA key from a JSON object in to a cjose JWK object */ static apr_byte_t oidc_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err) { apr_byte_t rv = FALSE; /* get the "x5c" array element from the JSON object */ json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); if (v == NULL) { oidc_jose_error(err, "JSON key \"%s\" could not be found", OIDC_JOSE_HDR_X5C); return FALSE; } if (!json_is_array(v)) { oidc_jose_error(err, "JSON key \"%s\" was found but its value is not a JSON array", OIDC_JOSE_HDR_X5C); return FALSE; } /* take the first element of the array */ v = json_array_get(v, 0); if (v == NULL) { oidc_jose_error(err, "first element in JSON array is \"null\""); return FALSE; } if (!json_is_string(v)) { oidc_jose_error(err, "first element in array is not a JSON string"); return FALSE; } const char *s_x5c = json_string_value(v); /* PEM-format it */ const int len = 75; int i = 0; char *s = apr_psprintf(pool, "%s\n", OIDC_JOSE_CERT_BEGIN); while (i < strlen(s_x5c)) { s = apr_psprintf(pool, "%s%s\n", s, apr_pstrmemdup(pool, s_x5c + i, len)); i += len; } s = apr_psprintf(pool, "%s%s\n", s, OIDC_JOSE_CERT_END); BIO *input = NULL; /* put it in BIO memory */ if ((input = BIO_new(BIO_s_mem())) == NULL) { oidc_jose_error_openssl(err, "memory allocation BIO_new/BIO_s_mem"); return FALSE; } if (BIO_puts(input, s) <= 0) { BIO_free(input); oidc_jose_error_openssl(err, "BIO_puts"); return FALSE; } /* do the actual parsing */ rv = oidc_jwk_rsa_bio_to_jwk(pool, input, NULL, jwk, FALSE, err); BIO_free(input); return rv; } /* * parse an X.509 PEM formatted certificate file with an RSA public key to a JWK struct */ apr_byte_t oidc_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { return oidc_jwk_parse_rsa_key(pool, TRUE, kid, filename, jwk, err); } /* * parse an X.509 PEM formatted RSA private key file to a JWK */ apr_byte_t oidc_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { return oidc_jwk_parse_rsa_key(pool, FALSE, kid, filename, jwk, err); } mod_auth_openidc-2.3.3/src/parse.c0000644000076500000240000010701213203320522016714 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Validation and parsing of configuration values. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include #include "mod_auth_openidc.h" #include "parse.h" #include "jose.h" /* * parse a URL according to one of two schemes (NULL for any) */ static const char * oidc_valid_url_scheme(apr_pool_t *pool, const char *arg, const char *scheme1, const char *scheme2) { apr_uri_t uri; if (apr_uri_parse(pool, arg, &uri) != APR_SUCCESS) { return apr_psprintf(pool, "'%s' cannot be parsed as a URL", arg); } if (uri.scheme == NULL) { return apr_psprintf(pool, "'%s' cannot be parsed as a URL (no scheme set)", arg); } if ((scheme1 != NULL) && (apr_strnatcmp(uri.scheme, scheme1) != 0)) { if ((scheme2 != NULL) && (apr_strnatcmp(uri.scheme, scheme2) != 0)) { return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" or \"%s\" URL (scheme == %s)!", arg, scheme1, scheme2, uri.scheme); } else if (scheme2 == NULL) { return apr_psprintf(pool, "'%s' cannot be parsed as a \"%s\" URL (scheme == %s)!", arg, scheme1, uri.scheme); } } if (uri.hostname == NULL) { return apr_psprintf(pool, "'%s' cannot be parsed as a valid URL (no hostname set, check your slashes)", arg); } return NULL; } /* * parse a URL according to a scheme */ const char *oidc_valid_url(apr_pool_t *pool, const char *arg, const char *scheme) { return oidc_valid_url_scheme(pool, arg, scheme, NULL); } /* * parse a URL that should conform to any HTTP scheme (http/https) */ const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg) { return oidc_valid_url_scheme(pool, arg, "https", "http"); } #define STR_ERROR_MAX 128 /* * check if arg is a valid directory on the file system */ const char *oidc_valid_dir(apr_pool_t *pool, const char *arg) { char s_err[STR_ERROR_MAX]; apr_dir_t *dir = NULL; apr_status_t rc = APR_SUCCESS; /* ensure the directory exists */ if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) { return apr_psprintf(pool, "cannot access directory '%s' (%s)", arg, apr_strerror(rc, s_err, STR_ERROR_MAX)); } /* and cleanup... */ if ((rc = apr_dir_close(dir)) != APR_SUCCESS) { return apr_psprintf(pool, "cannot close directory '%s' (%s)", arg, apr_strerror(rc, s_err, STR_ERROR_MAX)); } return NULL; } /* * check if arg is a valid cookie domain value */ const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg) { size_t sz, limit; char d; limit = strlen(arg); for (sz = 0; sz < limit; sz++) { d = arg[sz]; if ((d < '0' || d > '9') && (d < 'a' || d > 'z') && (d < 'A' || d > 'Z') && d != '.' && d != '-') { return (apr_psprintf(pool, "invalid character '%c' in cookie domain value: %s", d, arg)); } } return NULL; } /* * parse an integer value from a string */ const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value) { char *endptr; int v = strtol(arg, &endptr, 10); if ((*arg == '\0') || (*endptr != '\0')) { return apr_psprintf(pool, "invalid integer value: %s", arg); } *int_value = v; return NULL; } /* * check if the provided integer value is between a specified minimum and maximum */ static const char *oidc_valid_int_min_max(apr_pool_t *pool, int value, int min_value, int max_value) { if (value < min_value) { return apr_psprintf(pool, "integer value %d is smaller than the minimum allowed value %d", value, min_value); } if (value > max_value) { return apr_psprintf(pool, "integer value %d is greater than the maximum allowed value %d", value, max_value); } return NULL; } /* * parse an integer and check validity */ static const char *oidc_parse_int_valid(apr_pool_t *pool, const char *arg, int *int_value, oidc_valid_int_function_t valid_int_function) { int v = 0; const char *rv = NULL; rv = oidc_parse_int(pool, arg, &v); if (rv != NULL) return rv; rv = valid_int_function(pool, v); if (rv != NULL) return rv; *int_value = v; return NULL; } /* * parse an integer value from a string that must be between a specified minimum and maximum */ static const char *oidc_parse_int_min_max(apr_pool_t *pool, const char *arg, int *int_value, int min_value, int max_value) { int v = 0; const char *rv = NULL; rv = oidc_parse_int(pool, arg, &v); if (rv != NULL) return rv; rv = oidc_valid_int_min_max(pool, v, min_value, max_value); if (rv != NULL) return rv; *int_value = v; return NULL; } #define OIDC_LIST_OPTIONS_START "[" #define OIDC_LIST_OPTIONS_END "]" #define OIDC_LIST_OPTIONS_SEPARATOR "|" #define OIDC_LIST_OPTIONS_QUOTE "'" /* * flatten the list of string options, separated by the specified separator char */ static char *oidc_flatten_list_options(apr_pool_t *pool, char *options[]) { int i = 0; char *result = OIDC_LIST_OPTIONS_START; while (options[i] != NULL) { if (i == 0) result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_START, OIDC_LIST_OPTIONS_QUOTE, options[i], OIDC_LIST_OPTIONS_QUOTE); else result = apr_psprintf(pool, "%s%s%s%s%s", result, OIDC_LIST_OPTIONS_SEPARATOR, OIDC_LIST_OPTIONS_QUOTE, options[i], OIDC_LIST_OPTIONS_QUOTE); i++; } result = apr_psprintf(pool, "%s%s", result, OIDC_LIST_OPTIONS_END); return result; } /* * check if arg is a valid option in the list of provided string options */ static const char *oidc_valid_string_option(apr_pool_t *pool, const char *arg, char *options[]) { int i = 0; while (options[i] != NULL) { if (apr_strnatcmp(arg, options[i]) == 0) break; i++; } if (options[i] == NULL) { return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s", OIDC_LIST_OPTIONS_QUOTE, arg, OIDC_LIST_OPTIONS_QUOTE, oidc_flatten_list_options(pool, options)); } return NULL; } #define OIDC_CACHE_TYPE_SHM "shm" #define OIDC_CACHE_TYPE_MEMCACHE "memcache" #define OIDC_CACHE_TYPE_REDIS "redis" #define OIDC_CACHE_TYPE_FILE "file" /* * parse the cache backend type */ const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg, oidc_cache_t **type) { static char *options[] = { OIDC_CACHE_TYPE_SHM, OIDC_CACHE_TYPE_MEMCACHE, #ifdef USE_LIBHIREDIS OIDC_CACHE_TYPE_REDIS, #endif OIDC_CACHE_TYPE_FILE, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_SHM) == 0) { *type = &oidc_cache_shm; } else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_MEMCACHE) == 0) { *type = &oidc_cache_memcache; } else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_FILE) == 0) { *type = &oidc_cache_file; #ifdef USE_LIBHIREDIS } else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_REDIS) == 0) { *type = &oidc_cache_redis; #endif } return NULL; } #define OIDC_SESSION_TYPE_SERVER_CACHE_STR "server-cache" #define OIDC_SESSION_TYPE_CLIENT_COOKIE_STR "client-cookie" #define OIDC_SESSION_TYPE_PERSISTENT "persistent" #define OIDC_SESSION_TYPE_SEPARATOR ":" /* * parse the session mechanism type and the cookie persistency property */ const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg, int *type, int *persistent) { static char *options[] = { OIDC_SESSION_TYPE_SERVER_CACHE_STR, OIDC_SESSION_TYPE_SERVER_CACHE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; char *s = apr_pstrdup(pool, arg); char *p = strstr(s, OIDC_SESSION_TYPE_SEPARATOR); if (p) { *persistent = 1; *p = '\0'; } if (apr_strnatcmp(s, OIDC_SESSION_TYPE_SERVER_CACHE_STR) == 0) { *type = OIDC_SESSION_TYPE_SERVER_CACHE; } else if (apr_strnatcmp(s, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR) == 0) { *type = OIDC_SESSION_TYPE_CLIENT_COOKIE; } return NULL; } /* minimum size of a SHM cache entry */ #define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 17 // 8Kb plus overhead /* maximum size of a SHM cache entry */ #define OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX 1024 * 512 // 512Kb incl. overhead /* * parse the slot size of a SHM cache entry */ const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_min_max(pool, arg, int_value, OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX, OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX); } /* * parse a boolean value from a provided string */ const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value) { if ((apr_strnatcasecmp(arg, "true") == 0) || (apr_strnatcasecmp(arg, "on") == 0) || (apr_strnatcasecmp(arg, "yes") == 0) || (apr_strnatcasecmp(arg, "1") == 0)) { *bool_value = TRUE; return NULL; } if ((apr_strnatcasecmp(arg, "false") == 0) || (apr_strnatcasecmp(arg, "off") == 0) || (apr_strnatcasecmp(arg, "no") == 0) || (apr_strnatcasecmp(arg, "0") == 0)) { *bool_value = FALSE; return NULL; } return apr_psprintf(pool, "oidc_parse_boolean: could not parse boolean value from \"%s\"", arg); } #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST "client_secret_post" #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC "client_secret_basic" #define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT "client_secret_jwt" #define OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT "private_key_jwt" #define OIDC_ENDPOINT_AUTH_NONE "none" /* * check if the provided endpoint authentication method is supported */ static const char *oidc_valid_endpoint_auth_method_impl(apr_pool_t *pool, const char *arg, apr_byte_t has_private_key) { static char *options[] = { OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC, OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT, OIDC_ENDPOINT_AUTH_NONE, NULL, NULL }; if (has_private_key) options[3] = OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT; return oidc_valid_string_option(pool, arg, options); } const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, TRUE); } const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg) { return oidc_valid_endpoint_auth_method_impl(pool, arg, FALSE); } /* * check if the provided OAuth/OIDC response type is supported */ const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg) { if (oidc_proto_flow_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "oidc_valid_response_type: type must be one of %s", apr_array_pstrcat(pool, oidc_proto_supported_flows(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided PKCE method is supported */ const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_PKCE_METHOD_PLAIN, OIDC_PKCE_METHOD_S256, OIDC_PKCE_METHOD_REFERRED_TB, NULL }; return oidc_valid_string_option(pool, arg, options); } #define OIDC_RESPONSE_TYPE_FRAGMENT "fragment" #define OIDC_RESPONSE_TYPE_QUERY "query" #define OIDC_RESPONSE_TYPE_FORM_POST "form_post" /* * check if the provided OAuth 2.0 response mode is supported */ const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_RESPONSE_TYPE_FRAGMENT, OIDC_RESPONSE_TYPE_QUERY, OIDC_RESPONSE_TYPE_FORM_POST, NULL }; return oidc_valid_string_option(pool, arg, options); } /* * check if the provided JWT signature algorithm is supported */ const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg) { if (oidc_jose_jws_algorithm_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid signing algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jws_supported_algorithms(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided JWT content key encryption algorithm is supported */ const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) { if (oidc_jose_jwe_algorithm_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid encryption algorithm '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jwe_supported_algorithms(pool), OIDC_CHAR_PIPE)); } return NULL; } /* * check if the provided JWT encryption cipher is supported */ const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) { if (oidc_jose_jwe_encryption_is_supported(pool, arg) == FALSE) { return apr_psprintf(pool, "unsupported/invalid encryption type '%s'; must be one of [%s]", arg, apr_array_pstrcat(pool, oidc_jose_jwe_supported_encryptions(pool), OIDC_CHAR_PIPE)); } return NULL; } #define OIDC_SESSION_INACTIVITY_TIMEOUT_MIN 10 #define OIDC_SESSION_INACTIVITY_TIMEOUT_MAX 3600 * 24 * 365 /* * parse a session inactivity timeout value from the provided string */ const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_min_max(pool, arg, int_value, OIDC_SESSION_INACTIVITY_TIMEOUT_MIN, OIDC_SESSION_INACTIVITY_TIMEOUT_MAX); } #define OIDC_SESSION_MAX_DURATION_MIN 15 #define OIDC_SESSION_MAX_DURATION_MAX 3600 * 24 * 365 /* * check the boundaries for session max lifetime */ const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v) { if (v == 0) { return NULL; } if (v < OIDC_SESSION_MAX_DURATION_MIN) { return apr_psprintf(pool, "value must not be less than %d seconds", OIDC_SESSION_MAX_DURATION_MIN); } if (v > OIDC_SESSION_MAX_DURATION_MAX) { return apr_psprintf(pool, "value must not be greater than %d seconds", OIDC_SESSION_MAX_DURATION_MAX); } return NULL; } /* * parse a session max duration value from the provided string */ const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_session_max_duration); } /* * parse a base64 encoded binary value from the provided string */ static char *oidc_parse_base64(apr_pool_t *pool, const char *input, char **output, int *output_len) { int len = apr_base64_decode_len(input); *output = apr_palloc(pool, len); *output_len = apr_base64_decode(*output, input); if (*output_len <= 0) return apr_psprintf(pool, "base64-decoding of \"%s\" failed", input); return NULL; } /* * parse a base64url encoded binary value from the provided string */ static char *oidc_parse_base64url(apr_pool_t *pool, const char *input, char **output, int *output_len) { *output_len = oidc_base64url_decode(pool, output, input); if (*output_len <= 0) return apr_psprintf(pool, "base64url-decoding of \"%s\" failed", input); return NULL; } /* * parse a hexadecimal encoded binary value from the provided string */ static char *oidc_parse_hex(apr_pool_t *pool, const char *input, char **output, int *output_len) { *output_len = strlen(input) / 2; const char *pos = input; unsigned char *val = apr_palloc(pool, *output_len); size_t count = 0; for (count = 0; count < (*output_len) / sizeof(unsigned char); count++) { sscanf(pos, "%2hhx", &val[count]); pos += 2; } *output = (char*) val; return NULL; } #define OIDC_KEY_ENCODING_BASE64 "b64" #define OIDC_KEY_ENCODING_BASE64_URL "b64url" #define OIDC_KEY_ENCODING_HEX "hex" #define OIDC_KEY_ENCODING_PLAIN "plain" /* * parse a key value based on the provided encoding: b64|b64url|hex|plain */ static const char *oidc_parse_key_value(apr_pool_t *pool, const char *enc, const char *input, char **key, int *key_len) { static char *options[] = { OIDC_KEY_ENCODING_BASE64, OIDC_KEY_ENCODING_BASE64_URL, OIDC_KEY_ENCODING_HEX, OIDC_KEY_ENCODING_PLAIN, NULL }; const char *rv = oidc_valid_string_option(pool, enc, options); if (rv != NULL) return rv; if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0) return oidc_parse_base64(pool, input, key, key_len); if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0) return oidc_parse_base64url(pool, input, key, key_len); if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_HEX) == 0) return oidc_parse_hex(pool, input, key, key_len); if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_PLAIN) == 0) { *key = apr_pstrdup(pool, input); *key_len = strlen(*key); } return NULL; } #define OIDC_KEY_TUPLE_SEPARATOR "#" /* * parse a ## tuple */ const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, apr_byte_t triplet) { const char *rv = NULL; char *s = NULL, *p = NULL, *q = NULL, *enc = NULL; if ((tuple == NULL) || (apr_strnatcmp(tuple, "") == 0)) return "tuple value not set"; s = apr_pstrdup(pool, tuple); p = strstr(s, OIDC_KEY_TUPLE_SEPARATOR); if (p && triplet) q = strstr(p + 1, OIDC_KEY_TUPLE_SEPARATOR); if (p) { if (q) { *p = '\0'; *q = '\0'; enc = s; p++; if (p != q) *kid = apr_pstrdup(pool, p); rv = oidc_parse_key_value(pool, enc, q + 1, key, key_len); } else { *p = '\0'; *kid = s; *key = p + 1; *key_len = strlen(*key); } } else { *kid = NULL; *key = s; *key_len = strlen(*key); } return rv; } #define OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR "claims" #define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR "payload" #define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized" /* * convert a "pass id token as" value to an integer */ static int oidc_parse_pass_idtoken_as_str2int(const char *v) { if (apr_strnatcmp(v, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR) == 0) return OIDC_PASS_IDTOKEN_AS_CLAIMS; if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR) == 0) return OIDC_PASS_IDTOKEN_AS_PAYLOAD; if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR) == 0) return OIDC_PASS_IDTOKEN_AS_SERIALIZED; return -1; } /* * parse a "pass id token as" value from the provided strings */ const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value) { static char *options[] = { OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR, NULL }; const char *rv = NULL; rv = oidc_valid_string_option(pool, v1, options); if (rv != NULL) return rv; *int_value = oidc_parse_pass_idtoken_as_str2int(v1); if (v2 == NULL) return NULL; rv = oidc_valid_string_option(pool, v2, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_idtoken_as_str2int(v2); if (v3 == NULL) return NULL; rv = oidc_valid_string_option(pool, v3, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_idtoken_as_str2int(v3); return NULL; } #define OIDC_PASS_USERINFO_AS_CLAIMS_STR "claims" #define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json" #define OIDC_PASS_USERINFO_AS_JWT_STR "jwt" /* * convert a "pass userinfo as" value to an integer */ static int oidc_parse_pass_userinfo_as_str2int(const char *v) { if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_CLAIMS_STR) == 0) return OIDC_PASS_USERINFO_AS_CLAIMS; if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR) == 0) return OIDC_PASS_USERINFO_AS_JSON_OBJECT; if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JWT_STR) == 0) return OIDC_PASS_USERINFO_AS_JWT; return -1; } /* * parse a "pass id token as" value from the provided strings */ const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value) { static char *options[] = { OIDC_PASS_USERINFO_AS_CLAIMS_STR, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR, OIDC_PASS_USERINFO_AS_JWT_STR, NULL }; const char *rv = NULL; rv = oidc_valid_string_option(pool, v1, options); if (rv != NULL) return rv; *int_value = oidc_parse_pass_userinfo_as_str2int(v1); if (v2 == NULL) return NULL; rv = oidc_valid_string_option(pool, v2, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_userinfo_as_str2int(v2); if (v3 == NULL) return NULL; rv = oidc_valid_string_option(pool, v3, options); if (rv != NULL) return rv; *int_value |= oidc_parse_pass_userinfo_as_str2int(v3); return NULL; } #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR "post" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR "query" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR "cookie" /* * convert an "accept OAuth 2.0 token in" byte value to a string representation */ const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v) { static char *options[] = { NULL, NULL, NULL, NULL, NULL }; int i = 0; if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR; i++; } if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) { options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR; i++; } return oidc_flatten_list_options(pool, options); } /* * convert an "accept OAuth 2.0 token in" value to an integer */ static apr_byte_t oidc_parse_oauth_accept_token_in_str2byte(const char *v) { if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER; if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_POST; if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR) == 0) return OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY; if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR) == v) return OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE; return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT; } #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global" #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR ":" /* * parse an "accept OAuth 2.0 token in" value from the provided string */ const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options) { static char *options[] = { OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR, NULL }; const char *rv = NULL; const char *s = apr_pstrdup(pool, arg); char *p = strstr(s, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR); if (p != NULL) { *p = '\0'; p++; } else { p = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT; } apr_hash_set(list_options, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME, APR_HASH_KEY_STRING, p); rv = oidc_valid_string_option(pool, s, options); if (rv != NULL) return rv; int v = oidc_parse_oauth_accept_token_in_str2byte(s); if (*b_value == OIDC_CONFIG_POS_INT_UNSET) *b_value = v; else *b_value |= v; return NULL; } /* * check if the specified string is a valid claim formatting configuration value */ const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_CLAIM_FORMAT_RELATIVE, OIDC_CLAIM_FORMAT_ABSOLUTE, NULL }; return oidc_valid_string_option(pool, arg, options); } /* * parse a "claim required" value from the provided string */ const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required) { static char *options[] = { OIDC_CLAIM_REQUIRED_MANDATORY, OIDC_CLAIM_REQUIRED_OPTIONAL, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; *is_required = (apr_strnatcmp(arg, OIDC_CLAIM_REQUIRED_MANDATORY) == 0); return NULL; } /* * check if the provided string is a valid HTTP method for the OAuth token introspection endpoint */ const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_INTROSPECTION_METHOD_GET, OIDC_INTROSPECTION_METHOD_POST, NULL }; return oidc_valid_string_option(pool, arg, options); } #define OIDC_PASS_CLAIMS_AS_BOTH "both" #define OIDC_PASS_CLAIMS_AS_HEADERS "headers" #define OIDC_PASS_CLAIMS_AS_ENV "environment" #define OIDC_PASS_CLAIMS_AS_NONE "none" /* * parse a "set claims as" value from the provided string */ const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg, int *in_headers, int *in_env_vars) { static char *options[] = { OIDC_PASS_CLAIMS_AS_BOTH, OIDC_PASS_CLAIMS_AS_HEADERS, OIDC_PASS_CLAIMS_AS_ENV, OIDC_PASS_CLAIMS_AS_NONE, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_BOTH) == 0) { *in_headers = 1; *in_env_vars = 1; } else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_HEADERS) == 0) { *in_headers = 1; *in_env_vars = 0; } else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_ENV) == 0) { *in_headers = 0; *in_env_vars = 1; } else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_NONE) == 0) { *in_headers = 0; *in_env_vars = 0; } return NULL; } #define OIDC_UNAUTH_ACTION_AUTH_STR "auth" #define OIDC_UNAUTH_ACTION_PASS_STR "pass" #define OIDC_UNAUTH_ACTION_401_STR "401" #define OIDC_UNAUTH_ACTION_410_STR "410" /* * parse an "unauthenticated action" value from the provided string */ const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg, int *action) { static char *options[] = { OIDC_UNAUTH_ACTION_AUTH_STR, OIDC_UNAUTH_ACTION_PASS_STR, OIDC_UNAUTH_ACTION_401_STR, OIDC_UNAUTH_ACTION_410_STR, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_AUTH_STR) == 0) *action = OIDC_UNAUTH_AUTHENTICATE; else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_PASS_STR) == 0) *action = OIDC_UNAUTH_PASS; else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_401_STR) == 0) *action = OIDC_UNAUTH_RETURN401; else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_410_STR) == 0) *action = OIDC_UNAUTH_RETURN410; return NULL; } #define OIDC_UNAUTZ_ACTION_AUTH_STR "auth" #define OIDC_UNAUTZ_ACTION_401_STR "401" #define OIDC_UNAUTZ_ACTION_403_STR "403" /* * parse an "unauthorized action" value from the provided string */ const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg, int *action) { static char *options[] = { OIDC_UNAUTZ_ACTION_AUTH_STR, OIDC_UNAUTZ_ACTION_401_STR, OIDC_UNAUTZ_ACTION_403_STR, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_AUTH_STR) == 0) *action = OIDC_UNAUTZ_AUTHENTICATE; else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_401_STR) == 0) *action = OIDC_UNAUTZ_RETURN401; else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_403_STR) == 0) *action = OIDC_UNAUTZ_RETURN403; return NULL; } /* * check if there's one valid entry in a string of arrays */ const char *oidc_valid_string_in_array(apr_pool_t *pool, json_t *json, const char *key, oidc_valid_function_t valid_function, char **value, apr_byte_t optional) { int i = 0; json_t *json_arr = json_object_get(json, key); if ((json_arr != NULL) && (json_is_array(json_arr))) { for (i = 0; i < json_array_size(json_arr); i++) { json_t *elem = json_array_get(json_arr, i); if (!json_is_string(elem)) { return apr_psprintf(pool, "unhandled in-array JSON non-string object type [%d]", elem->type); continue; } if (valid_function(pool, json_string_value(elem)) == NULL) { if (value != NULL) *value = apr_pstrdup(pool, json_string_value(elem)); break; } } if (i == json_array_size(json_arr)) { return apr_psprintf(pool, "could not find a valid array string element for entry \"%s\"", key); } } else if (optional == FALSE) { return apr_psprintf(pool, "JSON object did not contain a \"%s\" array", key); } return NULL; } #define OIDC_JWKS_REFRESH_INTERVAL_MIN 300 #define OIDC_JWKS_REFRESH_INTERVAL_MAX 3600 * 24 * 365 /* * check the boundaries for JWKs refresh interval */ const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_JWKS_REFRESH_INTERVAL_MIN, OIDC_JWKS_REFRESH_INTERVAL_MAX); } /* * parse a JWKs refresh interval from the provided string */ const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_jwks_refresh_interval); } #define OIDC_IDTOKEN_IAT_SLACK_MIN 0 #define OIDC_IDTOKEN_IAT_SLACK_MAX 3600 /* * check the boundaries for ID token "issued-at" (iat) timestamp slack */ const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_IDTOKEN_IAT_SLACK_MIN, OIDC_IDTOKEN_IAT_SLACK_MAX); } /* * parse an ID token "iat" slack interval */ const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_idtoken_iat_slack); } #define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0 #define OIDC_USERINFO_REFRESH_INTERVAL_MAX 3600 * 24 * 365 /* * check the boundaries for the userinfo refresh interval */ const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v) { return oidc_valid_int_min_max(pool, v, OIDC_USERINFO_REFRESH_INTERVAL_MIN, OIDC_USERINFO_REFRESH_INTERVAL_MAX); } /* * parse a userinfo refresh interval from the provided string */ const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value) { return oidc_parse_int_valid(pool, arg, int_value, oidc_valid_userinfo_refresh_interval); } #define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header" #define OIDC_USER_INFO_TOKEN_METHOD_POST_STR "post_param" /* * check if the provided string is a valid userinfo token presentation method */ const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR, OIDC_USER_INFO_TOKEN_METHOD_POST_STR, NULL }; return oidc_valid_string_option(pool, arg, options); } /* * parse a userinfo token method string value to an integer */ const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, int *int_value) { const char *rv = oidc_valid_userinfo_token_method(pool, arg); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR) == 0) *int_value = OIDC_USER_INFO_TOKEN_METHOD_HEADER; if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_POST_STR) == 0) *int_value = OIDC_USER_INFO_TOKEN_METHOD_POST; return NULL; } /* * parse an "info hook data" value from the provided string */ const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_hash_t **hook_data) { static char *options[] = { OIDC_HOOK_INFO_TIMESTAMP, OIDC_HOOK_INFO_ACCES_TOKEN, OIDC_HOOK_INFO_ACCES_TOKEN_EXP, OIDC_HOOK_INFO_ID_TOKEN, OIDC_HOOK_INFO_USER_INFO, OIDC_HOOK_INFO_REFRESH_TOKEN, OIDC_HOOK_INFO_SESSION, NULL }; const char *rv = oidc_valid_string_option(pool, arg, options); if (rv != NULL) return rv; if (*hook_data == NULL) *hook_data = apr_hash_make(pool); apr_hash_set(*hook_data, arg, APR_HASH_KEY_STRING, arg); return NULL; } #define OIDC_TOKEN_BINDING_POLICY_DISABLED_STR "disabled" #define OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR "optional" #define OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR "required" #define OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR "enforced" const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v) { if (v == OIDC_TOKEN_BINDING_POLICY_DISABLED) return OIDC_TOKEN_BINDING_POLICY_DISABLED; if (v == OIDC_TOKEN_BINDING_POLICY_OPTIONAL) return OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR; if (v == OIDC_TOKEN_BINDING_POLICY_REQUIRED) return OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR; if (v == OIDC_TOKEN_BINDING_POLICY_ENFORCED) return OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR; return NULL; } /* * check token binding policy string value */ const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_TOKEN_BINDING_POLICY_DISABLED_STR, OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR, OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR, OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR, NULL }; return oidc_valid_string_option(pool, arg, options); } /* * parse token binding policy */ const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *policy) { const char *rv = oidc_valid_token_binding_policy(pool, arg); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_DISABLED_STR) == 0) *policy = OIDC_TOKEN_BINDING_POLICY_DISABLED; else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR) == 0) *policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL; else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR) == 0) *policy = OIDC_TOKEN_BINDING_POLICY_REQUIRED; else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR) == 0) *policy = OIDC_TOKEN_BINDING_POLICY_ENFORCED; return NULL; } #define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET" #define OIDC_AUTH_REQEUST_METHOD_POST_STR "POST" /* * parse method for sending the authentication request */ const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg) { static char *options[] = { OIDC_AUTH_REQUEST_METHOD_GET_STR, OIDC_AUTH_REQEUST_METHOD_POST_STR, NULL }; return oidc_valid_string_option(pool, arg, options); } /* * parse method for sending the authentication request */ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method) { const char *rv = oidc_valid_auth_request_method(pool, arg); if (rv != NULL) return rv; if (apr_strnatcmp(arg, OIDC_AUTH_REQUEST_METHOD_GET_STR) == 0) *method = OIDC_AUTH_REQUEST_METHOD_GET; else if (apr_strnatcmp(arg, OIDC_AUTH_REQEUST_METHOD_POST_STR) == 0) *method = OIDC_AUTH_REQUEST_METHOD_POST; return NULL; } mod_auth_openidc-2.3.3/src/pcre_subst.c0000644000076500000240000001115413200625410017755 0ustar hzandbeltstaff/************************************************* * PCRE string replacement * *************************************************/ /* PCRE is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. pcre_subst is a wrapper around pcre_exec designed to make it easier to perform PERL style replacements with PCRE. Written by: Bert Driehuis Copyright (c) 2000 Bert Driehuis ----------------------------------------------------------------------------- Permission is granted to anyone to use this software for any purpose on any computer system, and to redistribute it freely, subject to the following restrictions: 1. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 2. The origin of this software must not be misrepresented, either by explicit claim or by omission. 3. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. If PCRE is embedded in any software that is released under the GNU General Purpose Licence (GPL), then the terms of that licence shall supersede any condition above with which it is incompatible. */ #include #include #include #include #include "pcre_subst.h" #define MAXCAPTURE 50 #ifdef DEBUG_PCRE_SUBST static void dumpstr(const char *str, int len, int start, int end) { int i; for (i = 0; i < strlen(str); i++) { if (i >= start && i < end) putchar(str[i]); else putchar('-'); } putchar('\n'); } static void dumpmatch(const char *str, int len, const char *rep, int nmat, const int *ovec) { int i; printf("%s Input\n", str); printf("nmat=%d", nmat); for (i = 0; i < nmat * 2; i++) printf(" %d", ovec[i]); printf("\n"); for (i = 0; i < nmat * 2; i += 2) dumpstr(str, len, ovec[i], ovec[i+1]); printf("\n"); } #endif static int findreplen(const char *rep, int nmat, const int *replen) { int len = 0; int val; char *cp = (char *)rep; while(*cp) { if (*cp == '$' && isdigit(cp[1])) { val = strtoul(&cp[1], &cp, 10); if (val && val <= nmat + 1) len += replen[val -1]; else fprintf(stderr, "repl %d out of range\n", val); } else { cp++; len++; } } return len; } static void doreplace(char *out, const char *rep, int nmat, int *replen, const char **repstr) { int val; char *cp = (char *)rep; while(*cp) { if (*cp == '$' && isdigit(cp[1])) { val = strtoul(&cp[1], &cp, 10); if (val && val <= nmat + 1) { strncpy(out, repstr[val - 1], replen[val - 1]); out += replen[val -1]; } } else { *out++ = *cp++; } } } static char * edit(const char *str, int len, const char *rep, int nmat, const int *ovec) { int i, slen, rlen; const int *mvec = ovec; char *res, *cp; int replen[MAXCAPTURE]; const char *repstr[MAXCAPTURE]; nmat--; ovec += 2; for (i = 0; i < nmat; i++) { replen[i] = ovec[i * 2 + 1] - ovec[i * 2]; repstr[i] = &str[ovec[i * 2]]; #ifdef DEBUG_PCRE_SUBST printf(">>>%d %d %.*s\n", i, replen[i], replen[i], repstr[i]); #endif } slen = len; len -= mvec[1] - mvec[0]; len += rlen = findreplen(rep, nmat, replen); #ifdef DEBUG_PCRE_SUBST printf("resulting length %d (srclen=%d)\n", len, slen); #endif cp = res = pcre_malloc(len + 1); if (mvec[0] > 0) { strncpy(cp, str, mvec[0]); cp += mvec[0]; } doreplace(cp, rep, nmat, replen, repstr); cp += rlen; if (mvec[1] < slen) strcpy(cp, &str[mvec[1]]); res[len] = 0; return res; } char * pcre_subst(const pcre *ppat, const pcre_extra *extra, const char *str, int len, int offset, int options, const char *rep) { int nmat; int ovec[MAXCAPTURE * 3]; nmat = pcre_exec(ppat, extra, str, len, offset, options, ovec, sizeof(ovec)); #ifdef DEBUG_PCRE_SUBST dumpmatch(str, len, rep, nmat, ovec); #endif if (nmat <= 0) return NULL; return(edit(str, len, rep, nmat, ovec)); } #ifdef DEBUG_BUILD int main() { char *pat = "quick\\s(\\w+)\\s(fox)"; char *rep = "$1ish $2"; char *str = "The quick brown foxy"; char *newstr; const char *err; int erroffset; pcre_extra *extra; pcre *ppat = pcre_compile(pat, 0, &err, &erroffset, NULL); if (ppat == NULL) { fprintf(stderr, "%s at %d\n", err, erroffset); exit(1); } extra = pcre_study(ppat, 0, &err); if (err != NULL) fprintf(stderr, "Study %s failed: %s\n", pat, err); newstr = pcre_subst(ppat, extra, str, strlen(str), 0, 0, rep); if (newstr) { printf("Newstr\t%s\n", newstr); pcre_free(newstr); } else { printf("No match\n"); } return 0; } #endif mod_auth_openidc-2.3.3/src/cache/redis.c0000644000076500000240000002714513200625410017764 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * caching using a Redis backend * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #include "apr_general.h" #include "apr_strings.h" #include #include #include #include "../mod_auth_openidc.h" #include "hiredis/hiredis.h" // TODO: proper Redis error reporting (server unreachable etc.) extern module AP_MODULE_DECLARE_DATA auth_openidc_module; typedef struct oidc_cache_cfg_redis_t { /* cache_type = redis: Redis ptr */ oidc_cache_mutex_t *mutex; char *host_str; apr_port_t port; char *passwd; } oidc_cache_cfg_redis_t; /* create the cache context */ static void *oidc_cache_redis_cfg_create(apr_pool_t *pool) { oidc_cache_cfg_redis_t *context = apr_pcalloc(pool, sizeof(oidc_cache_cfg_redis_t)); context->mutex = oidc_cache_mutex_create(pool); context->passwd = NULL; return context; } /* * initialize the Redis struct the specified Redis server */ static int oidc_cache_redis_post_config(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); if (cfg->cache_cfg != NULL) return APR_SUCCESS; oidc_cache_cfg_redis_t *context = oidc_cache_redis_cfg_create( s->process->pool); cfg->cache_cfg = context; apr_status_t rv = APR_SUCCESS; /* parse the host:post tuple from the configuration */ if (cfg->cache_redis_server == NULL) { oidc_serror(s, "cache type is set to \"redis\", but no valid " OIDCRedisCacheServer " setting was found"); return HTTP_INTERNAL_SERVER_ERROR; } char* scope_id; rv = apr_parse_addr_port(&context->host_str, &scope_id, &context->port, cfg->cache_redis_server, s->process->pool); if (rv != APR_SUCCESS) { oidc_serror(s, "failed to parse cache server: '%s'", cfg->cache_redis_server); return HTTP_INTERNAL_SERVER_ERROR; } if (context->host_str == NULL) { oidc_serror(s, "failed to parse cache server, no hostname specified: '%s'", cfg->cache_redis_server); return HTTP_INTERNAL_SERVER_ERROR; } if (context->port == 0) context->port = 6379; if (cfg->cache_redis_password != NULL) { context->passwd = apr_pstrdup(s->process->pool, cfg->cache_redis_password); } if (oidc_cache_mutex_post_config(s, context->mutex, "redis") == FALSE) return HTTP_INTERNAL_SERVER_ERROR; return OK; } /* * initialize the Redis cache in a child process */ int oidc_cache_redis_child_init(apr_pool_t *p, server_rec *s) { oidc_cfg *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg; /* initialize the lock for the child process */ return oidc_cache_mutex_child_init(p, s, context->mutex); } /* * assemble single key name based on section/key input */ static char *oidc_cache_redis_get_key(apr_pool_t *pool, const char *section, const char *key) { return apr_psprintf(pool, "%s:%s", section, key); } /* key for storing data in the process pool */ #define OIDC_CACHE_REDIS_CONTEXT "oidc_cache_redis_context" /* * per-process Redis connection context */ typedef struct { redisContext *ctx; } oidc_cache_redis_ctx_t; /* * free resources allocated for the per-process Redis connection context */ static apr_status_t oidc_cache_redis_free(void *ptr) { oidc_cache_redis_ctx_t *rctx = (oidc_cache_redis_ctx_t *) ptr; if ((rctx != NULL) && (rctx->ctx != NULL)) { redisFree(rctx->ctx); rctx->ctx = NULL; } return APR_SUCCESS; } /* * connect to Redis server */ static oidc_cache_redis_ctx_t * oidc_cache_redis_connect(request_rec *r, oidc_cache_cfg_redis_t *context) { /* see if we already have a connection by looking it up in the process context */ oidc_cache_redis_ctx_t *rctx = NULL; apr_pool_userdata_get((void **) &rctx, OIDC_CACHE_REDIS_CONTEXT, r->server->process->pool); if (rctx == NULL) { rctx = apr_pcalloc(r->server->process->pool, sizeof(oidc_cache_redis_ctx_t)); rctx->ctx = NULL; /* store the connection in the process context */ apr_pool_userdata_set(rctx, OIDC_CACHE_REDIS_CONTEXT, oidc_cache_redis_free, r->server->process->pool); } if (rctx->ctx == NULL) { /* no connection, connect to the configured Redis server */ rctx->ctx = redisConnect(context->host_str, context->port); /* check for errors */ if ((rctx->ctx == NULL) || (rctx->ctx->err != 0)) { oidc_error(r, "failed to connect to Redis server (%s:%d): '%s'", context->host_str, context->port, rctx->ctx != NULL ? rctx->ctx->errstr : ""); oidc_cache_redis_free(rctx); } else { /* log the connection */ oidc_debug(r, "successfully connected to Redis server (%s:%d)", context->host_str, context->port); } } return rctx; } /* * execute Redis command and deal with return value */ static redisReply* oidc_cache_redis_command(request_rec *r, oidc_cache_cfg_redis_t *context, const char *format, ...) { oidc_cache_redis_ctx_t *rctx = NULL; redisReply *reply = NULL; int i = 0; /* try to execute a command at max 2 times while reconnecting */ for (i = 0; i < 2; i++) { /* connect */ rctx = oidc_cache_redis_connect(r, context); if ((rctx == NULL) || (rctx->ctx == NULL)) break; if (context->passwd != NULL) { redisAppendCommand(rctx->ctx, apr_psprintf(r->pool, "AUTH %s", context->passwd)); } /* execute the command */ va_list args; va_start(args, format); redisvAppendCommand(rctx->ctx, format, args); va_end(args); if (context->passwd != NULL) { /* get the reply for the AUTH command */ redisGetReply(rctx->ctx, (void **) &reply); if (reply == NULL) { oidc_error(r, "authentication to the Redis server (%s:%d) failed, reply == NULL", context->host_str, context->port); } else if (reply->type == REDIS_REPLY_ERROR) { oidc_error(r, "authentication to the Redis server (%s:%d) failed, reply.status = %s", context->host_str, context->port, reply->str); } } /* get the reply for the actual command */ reply = NULL; redisGetReply(rctx->ctx, (void **) &reply); /* errors will result in an empty reply */ if (reply != NULL) { if (reply->type == REDIS_REPLY_ERROR) { oidc_error(r, "command to the Redis server (%s:%d) returned an error, reply.status = %s", context->host_str, context->port, reply->str); } break; } /* something went wrong, log it */ oidc_error(r, "redisvAppendCommand/redisGetReply (%d) failed, disconnecting: '%s'", i, rctx->ctx->errstr); /* cleanup, we may try again (once) after reconnecting */ oidc_cache_redis_free(rctx); } return reply; } /* * get a name/value pair from Redis */ static apr_byte_t oidc_cache_redis_get(request_rec *r, const char *section, const char *key, const char **value) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg; redisReply *reply = NULL; /* grab the global lock */ if (oidc_cache_mutex_lock(r, context->mutex) == FALSE) return FALSE; /* get */ reply = oidc_cache_redis_command(r, context, "GET %s", oidc_cache_redis_get_key(r->pool, section, key)); if (reply == NULL) { oidc_cache_mutex_unlock(r, context->mutex); return FALSE; } /* check that we got a string back */ if (reply->type != REDIS_REPLY_STRING) { freeReplyObject(reply); /* this is a normal cache miss, so we'll return OK */ oidc_cache_mutex_unlock(r, context->mutex); return TRUE; } /* do a sanity check on the returned value */ if (reply->len != strlen(reply->str)) { oidc_error(r, "redisCommand reply->len != strlen(reply->str): '%s'", reply->str); freeReplyObject(reply); oidc_cache_mutex_unlock(r, context->mutex); return FALSE; } /* copy it in to the request memory pool */ *value = apr_pstrdup(r->pool, reply->str); freeReplyObject(reply); /* release the global lock */ oidc_cache_mutex_unlock(r, context->mutex); return TRUE; } /* * store a name/value pair in Redis */ static apr_byte_t oidc_cache_redis_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry) { oidc_cfg *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg; redisReply *reply = NULL; /* grab the global lock */ if (oidc_cache_mutex_lock(r, context->mutex) == FALSE) return FALSE; /* see if we should be clearing this entry */ if (value == NULL) { /* delete it */ reply = oidc_cache_redis_command(r, context, "DEL %s", oidc_cache_redis_get_key(r->pool, section, key)); if (reply == NULL) { oidc_cache_mutex_unlock(r, context->mutex); return FALSE; } freeReplyObject(reply); } else { /* calculate the timeout from now */ apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now()); /* store it */ reply = oidc_cache_redis_command(r, context, "SETEX %s %d %s", oidc_cache_redis_get_key(r->pool, section, key), timeout, value); if (reply == NULL) { oidc_cache_mutex_unlock(r, context->mutex); return FALSE; } freeReplyObject(reply); } /* release the global lock */ oidc_cache_mutex_unlock(r, context->mutex); return TRUE; } static int oidc_cache_redis_destroy(server_rec *s) { oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config, &auth_openidc_module); oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg; oidc_cache_mutex_destroy(s, context->mutex); return APR_SUCCESS; } oidc_cache_t oidc_cache_redis = { "redis", 1, oidc_cache_redis_post_config, oidc_cache_redis_child_init, oidc_cache_redis_get, oidc_cache_redis_set, oidc_cache_redis_destroy }; mod_auth_openidc-2.3.3/src/mod_auth_openidc.h0000644000076500000240000012770213203320522021120 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #ifndef MOD_AUTH_OPENIDC_H_ #define MOD_AUTH_OPENIDC_H_ #include #include #include #include #include #include "jose.h" #include "cache/cache.h" #include "parse.h" #include #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(auth_openidc); #endif #ifndef OIDC_DEBUG #define OIDC_DEBUG APLOG_DEBUG #endif #define oidc_log(r, level, fmt, ...) ap_log_rerror(APLOG_MARK, level, 0, r,"%s: %s", __FUNCTION__, apr_psprintf(r->pool, fmt, ##__VA_ARGS__)) #define oidc_slog(s, level, fmt, ...) ap_log_error(APLOG_MARK, level, 0, s, "%s: %s", __FUNCTION__, apr_psprintf(s->process->pool, fmt, ##__VA_ARGS__)) #define oidc_debug(r, fmt, ...) oidc_log(r, OIDC_DEBUG, fmt, ##__VA_ARGS__) #define oidc_warn(r, fmt, ...) oidc_log(r, APLOG_WARNING, fmt, ##__VA_ARGS__) #define oidc_error(r, fmt, ...) oidc_log(r, APLOG_ERR, fmt, ##__VA_ARGS__) #define oidc_sdebug(s, fmt, ...) oidc_slog(s, OIDC_DEBUG, fmt, ##__VA_ARGS__) #define oidc_swarn(s, fmt, ...) oidc_slog(s, APLOG_WARNING, fmt, ##__VA_ARGS__) #define oidc_serror(s, fmt, ...) oidc_slog(s, APLOG_ERR, fmt, ##__VA_ARGS__) #ifndef NAMEVER #define NAMEVERSION "mod_auth_openidc-0.0.0" #else #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define NAMEVERSION TOSTRING(NAMEVER) #endif /* keys for storing info in the request state */ #define OIDC_REQUEST_STATE_KEY_IDTOKEN "i" #define OIDC_REQUEST_STATE_KEY_CLAIMS "c" /* parameter name of the callback URL in the discovery response */ #define OIDC_DISC_CB_PARAM "oidc_callback" /* parameter name of the OP provider selection in the discovery response */ #define OIDC_DISC_OP_PARAM "iss" /* parameter name of the user URL in the discovery response */ #define OIDC_DISC_USER_PARAM "disc_user" /* parameter name of the original URL in the discovery response */ #define OIDC_DISC_RT_PARAM "target_link_uri" /* parameter name of the original method in the discovery response */ #define OIDC_DISC_RM_PARAM "method" /* parameter name of login hint in the discovery response */ #define OIDC_DISC_LH_PARAM "login_hint" /* parameter name of parameters that need to be passed in the authentication request */ #define OIDC_DISC_AR_PARAM "auth_request_params" /* parameter name of the scopes required in the discovery response */ #define OIDC_DISC_SC_PARAM "scopes" /* value that indicates to use server-side cache based session tracking */ #define OIDC_SESSION_TYPE_SERVER_CACHE 0 /* value that indicates to use client cookie based session tracking */ #define OIDC_SESSION_TYPE_CLIENT_COOKIE 1 /* nonce bytes length */ #define OIDC_PROTO_NONCE_LENGTH 32 /* code verifier length */ #define OIDC_PROTO_CODE_VERIFIER_LENGTH 32 /* pass id_token as individual claims in headers (default) */ #define OIDC_PASS_IDTOKEN_AS_CLAIMS 1 /* pass id_token payload as JSON object in header */ #define OIDC_PASS_IDTOKEN_AS_PAYLOAD 2 /* pass id_token in compact serialized format in header */ #define OIDC_PASS_IDTOKEN_AS_SERIALIZED 4 /* pass userinfo as individual claims in headers (default) */ #define OIDC_PASS_USERINFO_AS_CLAIMS 1 /* pass userinfo payload as JSON object in header */ #define OIDC_PASS_USERINFO_AS_JSON_OBJECT 2 /* pass userinfo as a JWT in header (when returned as a JWT) */ #define OIDC_PASS_USERINFO_AS_JWT 4 #define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT 0 /* accept bearer token in header (default) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER 1 /* accept bearer token as a post parameter */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST 2 /* accept bearer token as a query parameter */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY 4 /* accept bearer token as a cookie parameter (PingAccess) */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE 8 /* the hash key of the cookie name value in the list of options */ #define OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME "cookie-name" /* introspection method options */ #define OIDC_INTROSPECTION_METHOD_GET "GET" #define OIDC_INTROSPECTION_METHOD_POST "POST" /* HTTP methods to send authentication requests */ #define OIDC_AUTH_REQUEST_METHOD_GET 0 #define OIDC_AUTH_REQUEST_METHOD_POST 1 /* prefix of the cookie that binds the state in the authorization request/response to the browser */ #define OIDC_STATE_COOKIE_PREFIX "mod_auth_openidc_state_" /* default prefix for information passed in HTTP headers */ #define OIDC_DEFAULT_HEADER_PREFIX "OIDC_" /* the (global) key for the mod_auth_openidc related state that is stored in the request userdata context */ #define OIDC_USERDATA_KEY "mod_auth_openidc_state" #define OIDC_USERDATA_ENV_KEY "mod_auth_openidc_env" /* input filter hook name */ #define OIDC_UTIL_HTTP_SENDSTRING "OIDC_UTIL_HTTP_SENDSTRING" /* the name of the keyword that follows the Require primitive to indicate claims-based authorization */ #define OIDC_REQUIRE_CLAIM_NAME "claim" #ifdef USE_LIBJQ /* the name of the keyword that follows the Require primitive to indicate claims-expression-based authorization */ #define OIDC_REQUIRE_CLAIMS_EXPR_NAME "claims_expr" #endif /* defines for how long provider metadata will be cached */ #define OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT 86400 /* define the parameter value for the "logout" request that indicates a GET-style logout call from the OP */ #define OIDC_GET_STYLE_LOGOUT_PARAM_VALUE "get" #define OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE "img" /* define the name of the cookie/parameter for CSRF protection */ #define OIDC_CSRF_NAME "x_csrf" /* http methods */ #define OIDC_METHOD_GET "get" #define OIDC_METHOD_FORM_POST "form_post" /* the maximum size of data that we accept in a single POST value: 1MB */ #define OIDC_MAX_POST_DATA_LEN 1024 * 1024 #define OIDC_UNAUTH_AUTHENTICATE 1 #define OIDC_UNAUTH_PASS 2 #define OIDC_UNAUTH_RETURN401 3 #define OIDC_UNAUTH_RETURN410 4 #define OIDC_UNAUTZ_RETURN403 1 #define OIDC_UNAUTZ_RETURN401 2 #define OIDC_UNAUTZ_AUTHENTICATE 3 #define OIDC_REQUEST_URI_CACHE_DURATION 30 #define OIDC_USER_INFO_TOKEN_METHOD_HEADER 0 #define OIDC_USER_INFO_TOKEN_METHOD_POST 1 #define OIDC_COOKIE_EXT_SAME_SITE_LAX "SameSite=Lax" #define OIDC_COOKIE_EXT_SAME_SITE_STRICT "SameSite=Strict" /* https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 */ #define OIDC_TB_CFG_PROVIDED_ENV_VAR "Sec-Provided-Token-Binding-ID" #define OIDC_TOKEN_BINDING_POLICY_DISABLED 0 #define OIDC_TOKEN_BINDING_POLICY_OPTIONAL 1 #define OIDC_TOKEN_BINDING_POLICY_REQUIRED 2 #define OIDC_TOKEN_BINDING_POLICY_ENFORCED 3 typedef apr_byte_t (*oidc_proto_pkce_state)(request_rec *r, char **state); typedef apr_byte_t (*oidc_proto_pkce_challenge)(request_rec *r, const char *state, char **code_challenge); typedef apr_byte_t (*oidc_proto_pkce_verifier)(request_rec *r, const char *state, char **code_verifier); typedef struct oidc_proto_pkce_t { const char *method; oidc_proto_pkce_state state; oidc_proto_pkce_verifier verifier; oidc_proto_pkce_challenge challenge; } oidc_proto_pkce_t; extern oidc_proto_pkce_t oidc_pkce_plain; extern oidc_proto_pkce_t oidc_pkce_s256; extern oidc_proto_pkce_t oidc_pkce_referred_tb; typedef struct oidc_jwks_uri_t { const char *url; int refresh_interval; int ssl_validate_server; } oidc_jwks_uri_t; typedef struct oidc_provider_t { char *metadata_url; char *issuer; char *authorization_endpoint_url; char *token_endpoint_url; char *token_endpoint_auth; char *token_endpoint_params; char *userinfo_endpoint_url; char *registration_endpoint_url; char *check_session_iframe; char *end_session_endpoint; char *jwks_uri; char *client_id; char *client_secret; char *token_endpoint_tls_client_key; char *token_endpoint_tls_client_cert; // the next ones function as global default settings too int ssl_validate_server; char *client_name; char *client_contact; char *registration_token; char *registration_endpoint_json; char *scope; char *response_type; char *response_mode; int jwks_refresh_interval; int idtoken_iat_slack; char *auth_request_params; int session_max_duration; oidc_proto_pkce_t *pkce; int userinfo_refresh_interval; char *client_jwks_uri; char *id_token_signed_response_alg; char *id_token_encrypted_response_alg; char *id_token_encrypted_response_enc; char *userinfo_signed_response_alg; char *userinfo_encrypted_response_alg; char *userinfo_encrypted_response_enc; int userinfo_token_method; char *request_object; int auth_request_method; int token_binding_policy; int issuer_specific_redirect_uri; } oidc_provider_t ; typedef struct oidc_remote_user_claim_t { const char *claim_name; const char *reg_exp; const char *replace; } oidc_remote_user_claim_t; typedef struct oidc_oauth_t { int ssl_validate_server; char *client_id; char *client_secret; char *introspection_endpoint_tls_client_key; char *introspection_endpoint_tls_client_cert; char *introspection_endpoint_url; char *introspection_endpoint_method; char *introspection_endpoint_params; char *introspection_endpoint_auth; char *introspection_client_auth_bearer_token; char *introspection_token_param_name; char *introspection_token_expiry_claim_name; char *introspection_token_expiry_claim_format; int introspection_token_expiry_claim_required; oidc_remote_user_claim_t remote_user_claim; apr_hash_t *verify_shared_keys; char *verify_jwks_uri; apr_hash_t *verify_public_keys; } oidc_oauth_t; typedef struct oidc_cfg { /* indicates whether this is a derived config, merged from a base one */ unsigned int merged; /* HTML to display error messages+description */ char *error_template; /* the redirect URI as configured with the OpenID Connect OP's that we talk to */ char *redirect_uri; /* (optional) default URL for 3rd-party initiated SSO */ char *default_sso_url; /* (optional) default URL to go to after logout */ char *default_slo_url; /* public keys in JWK format, used by parters for encrypting JWTs sent to us */ apr_hash_t *public_keys; /* private keys in JWK format used for decrypting encrypted JWTs sent to us */ apr_hash_t *private_keys; /* a pointer to the (single) provider that we connect to */ /* NB: if metadata_dir is set, these settings will function as defaults for the metadata read from there) */ oidc_provider_t provider; /* a pointer to the oauth server settings */ oidc_oauth_t oauth; /* directory that holds the provider & client metadata files */ char *metadata_dir; /* type of session management/storage */ int session_type; /* session cookie or persistent cookie */ int persistent_session_cookie; /* session cookie chunk size */ int session_cookie_chunk_size; /* pointer to cache functions */ oidc_cache_t *cache; void *cache_cfg; /* cache_type = file: directory that holds the cache files (if not set, we'll try and use an OS defined one like "/tmp" */ char *cache_file_dir; /* cache_type = file: clean interval */ int cache_file_clean_interval; /* cache_type= memcache: list of memcache host/port servers to use */ char *cache_memcache_servers; /* cache_type = shm: size of the shared memory segment (cq. max number of cached entries) */ int cache_shm_size_max; /* cache_type = shm: maximum size in bytes of a cache entry */ int cache_shm_entry_size_max; #ifdef USE_LIBHIREDIS /* cache_type= redis: Redis host/port server to use */ char *cache_redis_server; char *cache_redis_password; #endif int cache_encrypt; /* tell the module to strip any mod_auth_openidc related headers that already have been set by the user-agent, normally required for secure operation */ int scrub_request_headers; int http_timeout_long; int http_timeout_short; int state_timeout; int session_inactivity_timeout; int session_cache_fallback_to_cookie; char *cookie_domain; char *claim_delimiter; char *claim_prefix; oidc_remote_user_claim_t remote_user_claim; int pass_idtoken_as; int pass_userinfo_as; int cookie_http_only; int cookie_same_site; char *outgoing_proxy; char *crypto_passphrase; int provider_metadata_refresh_interval; apr_hash_t *info_hook_data; apr_hash_t *black_listed_claims; apr_hash_t *white_listed_claims; } oidc_cfg; int oidc_check_user_id(request_rec *r); #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args); #ifdef USE_LIBJQ authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args); #endif #else int oidc_auth_checker(request_rec *r); #endif void oidc_request_state_set(request_rec *r, const char *key, const char *value); const char*oidc_request_state_get(request_rec *r, const char *key); int oidc_handle_jwks(request_rec *r, oidc_cfg *c); apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method); void oidc_scrub_headers(request_rec *r); void oidc_strip_cookies(request_rec *r); int oidc_content_handler(request_rec *r); apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *replace, const char *reg_exp, json_t *json, char **request_user); #define OIDC_REDIRECT_URI_REQUEST_INFO "info" #define OIDC_REDIRECT_URI_REQUEST_LOGOUT "logout" #define OIDC_REDIRECT_URI_REQUEST_JWKS "jwks" #define OIDC_REDIRECT_URI_REQUEST_SESSION "session" #define OIDC_REDIRECT_URI_REQUEST_REFRESH "refresh" #define OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE "remove_at_cache" #define OIDC_REDIRECT_URI_REQUEST_REQUEST_URI "request_uri" // oidc_oauth int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c); apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token); // oidc_proto.c #define OIDC_PROTO_ISS "iss" #define OIDC_PROTO_CODE "code" #define OIDC_PROTO_CLIENT_ID "client_id" #define OIDC_PROTO_CLIENT_SECRET "client_secret" #define OIDC_PROTO_CLIENT_ASSERTION "client_assertion" #define OIDC_PROTO_CLIENT_ASSERTION_TYPE "client_assertion_type" #define OIDC_PROTO_ACCESS_TOKEN "access_token" #define OIDC_PROTO_ID_TOKEN "id_token" #define OIDC_PROTO_STATE "state" #define OIDC_PROTO_GRANT_TYPE "grant_type" #define OIDC_PROTO_REDIRECT_URI "redirect_uri" #define OIDC_PROTO_CODE_VERIFIER "code_verifier" #define OIDC_PROTO_CODE_CHALLENGE "code_challenge" #define OIDC_PROTO_CODE_CHALLENGE_METHOD "code_challenge_method" #define OIDC_PROTO_SCOPE "scope" #define OIDC_PROTO_REFRESH_TOKEN "refresh_token" #define OIDC_PROTO_TOKEN_TYPE "token_type" #define OIDC_PROTO_EXPIRES_IN "expires_in" #define OIDC_PROTO_RESPONSE_TYPE "response_type" #define OIDC_PROTO_RESPONSE_MODE "response_mode" #define OIDC_PROTO_NONCE "nonce" #define OIDC_PROTO_PROMPT "prompt" #define OIDC_PROTO_LOGIN_HINT "login_hint" #define OIDC_PROTO_ID_TOKEN_HINT "id_token_hint" #define OIDC_PROTO_REQUEST_URI "request_uri" #define OIDC_PROTO_REQUEST_OBJECT "request" #define OIDC_PROTO_SESSION_STATE "session_state" #define OIDC_PROTO_ACTIVE "active" #define OIDC_PROTO_RESPONSE_TYPE_CODE "code" #define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN "id_token" #define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN "id_token token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN "code id_token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN "code token" #define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN "code id_token token" #define OIDC_PROTO_RESPONSE_TYPE_TOKEN "token" #define OIDC_PROTO_RESPONSE_MODE_QUERY "query" #define OIDC_PROTO_RESPONSE_MODE_FRAGMENT "fragment" #define OIDC_PROTO_RESPONSE_MODE_FORM_POST "form_post" #define OIDC_PROTO_SCOPE_OPENID "openid" #define OIDC_PROTO_PROMPT_NONE "none" #define OIDC_PROTO_ERROR "error" #define OIDC_PROTO_ERROR_DESCRIPTION "error_description" #define OIDC_PROTO_REALM "realm" #define OIDC_PROTO_ERR_INVALID_TOKEN "invalid_token" #define OIDC_PROTO_ERR_INVALID_REQUEST "invalid_request" #define OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE "authorization_code" #define OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN "refresh_token" #define OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" #define OIDC_PROTO_CLIENT_SECRET_BASIC "client_secret_basic" #define OIDC_PROTO_CLIENT_SECRET_POST "client_secret_post" #define OIDC_PROTO_CLIENT_SECRET_JWT "client_secret_jwt" #define OIDC_PROTO_PRIVATE_KEY_JWT "private_key_jwt" #define OIDC_PROTO_ENDPOINT_AUTH_NONE "none" #define OIDC_PROTO_BEARER "Bearer" #define OIDC_CLAIM_ISS "iss" #define OIDC_CLAIM_AUD "aud" #define OIDC_CLAIM_AZP "azp" #define OIDC_CLAIM_SUB "sub" #define OIDC_CLAIM_JTI "jti" #define OIDC_CLAIM_EXP "exp" #define OIDC_CLAIM_IAT "iat" #define OIDC_CLAIM_NONCE "nonce" #define OIDC_CLAIM_AT_HASH "at_hash" #define OIDC_CLAIM_C_HASH "c_hash" #define OIDC_CLAIM_RFP "rfp" #define OIDC_CLAIM_TARGET_LINK_URI "target_link_uri" #define OIDC_JWK_X5T "x5t" #define OIDC_JWK_KEYS "keys" #define OIDC_JWK_USE "use" #define OIDC_JWK_SIG "sig" #define OIDC_JWK_ENC "enc" #define OIDC_HOOK_INFO_FORMAT_JSON "json" #define OIDC_HOOK_INFO_TIMESTAMP "iat" #define OIDC_HOOK_INFO_ACCES_TOKEN "access_token" #define OIDC_HOOK_INFO_ACCES_TOKEN_EXP "access_token_expires" #define OIDC_HOOK_INFO_ID_TOKEN "id_token" #define OIDC_HOOK_INFO_USER_INFO "userinfo" #define OIDC_HOOK_INFO_SESSION "session" #define OIDC_HOOK_INFO_SESSION_STATE "state" #define OIDC_HOOK_INFO_SESSION_UUID "uuid" #define OIDC_HOOK_INFO_SESSION_EXP "exp" #define OIDC_HOOK_INFO_SESSION_REMOTE_USER "remote_user" #define OIDC_HOOK_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_CONTENT_TYPE_JSON "application/json" #define OIDC_CONTENT_TYPE_JWT "application/jwt" #define OIDC_CONTENT_TYPE_FORM_ENCODED "application/x-www-form-urlencoded" #define OIDC_CONTENT_TYPE_IMAGE_PNG "image/png" #define OIDC_CONTENT_TYPE_HTML "text/html" #define OIDC_STR_SPACE " " #define OIDC_STR_EQUAL "=" #define OIDC_STR_AMP "&" #define OIDC_STR_QUERY "?" #define OIDC_STR_COLON ":" #define OIDC_STR_SEMI_COLON ";" #define OIDC_STR_FORWARD_SLASH "/" #define OIDC_STR_AT "@" #define OIDC_STR_COMMA "," #define OIDC_CHAR_EQUAL '=' #define OIDC_CHAR_COLON ':' #define OIDC_CHAR_TILDE '~' #define OIDC_CHAR_SPACE ' ' #define OIDC_CHAR_COMMA ',' #define OIDC_CHAR_QUERY '?' #define OIDC_CHAR_DOT '.' #define OIDC_CHAR_AT '@' #define OIDC_CHAR_FORWARD_SLASH '/' #define OIDC_CHAR_PIPE '|' #define OIDC_CHAR_AMP '&' #define OIDC_APP_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_APP_INFO_ACCESS_TOKEN "access_token" #define OIDC_APP_INFO_ACCESS_TOKEN_EXP "access_token_expires" #define OIDC_APP_INFO_ID_TOKEN "id_token" #define OIDC_APP_INFO_ID_TOKEN_PAYLOAD "id_token_payload" #define OIDC_APP_INFO_USERINFO_JSON "userinfo_json" #define OIDC_APP_INFO_USERINFO_JWT "userinfo_jwt" typedef json_t oidc_proto_state_t; oidc_proto_state_t *oidc_proto_state_new(); void oidc_proto_state_destroy(oidc_proto_state_t *proto_state); oidc_proto_state_t *oidc_proto_state_from_cookie(request_rec *r, oidc_cfg *c, const char *cookieValue); char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state); char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state); apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_original_method(oidc_proto_state_t *proto_state); const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state); void oidc_proto_state_set_state(oidc_proto_state_t *proto_state, const char *state); void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state, const char *issuer); void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state, const char *original_url); void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state, const char *original_method); void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state, const char *response_mode); void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state, const char *response_type); void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state, const char *nonce); void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state, const char *prompt); void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state); void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state); apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params, char **basic_auth_str, char **bearer_auth_str); char *oidc_proto_peek_jwt_header(request_rec *r, const char *jwt, char **alg); int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, const char *id_token_hint, const char *code_challenge, const char *auth_request_params, const char *path_scope); apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg); apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token); apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt); apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, char **issuer); apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg, const char *url, char **issuer); apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c); apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool); apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow); apr_byte_t oidc_proto_validate_authorization_response(request_rec *r, const char *response_type, const char *requested_response_mode, char **code, char **id_token, char **access_token, char **token_type, const char *used_response_mode); apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, apr_hash_t *symmetric_keys); apr_byte_t oidc_proto_validate_jwt(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, apr_byte_t iat_is_mandatory, int iat_slack); apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len); apr_byte_t oidc_proto_authorization_response_code_idtoken_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_authorization_response_code_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_handle_authorization_response_code_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_handle_authorization_response_code(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_handle_authorization_response_idtoken_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); apr_byte_t oidc_proto_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt); // non-static for test.c apr_byte_t oidc_proto_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *access_token); apr_byte_t oidc_proto_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *code); apr_byte_t oidc_proto_validate_nonce(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt); // oidc_authz.c typedef apr_byte_t (*oidc_authz_match_claim_fn_type)(request_rec *, const char * const, const json_t * const); apr_byte_t oidc_authz_match_claim(request_rec *r, const char * const attr_spec, const json_t * const claims); #ifdef USE_LIBJQ apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char * const attr_spec, const json_t * const claims); #endif #if MODULE_MAGIC_NUMBER_MAJOR < 20100714 int oidc_authz_worker22(request_rec *r, const json_t *const claims, const require_line *const reqs, int nelts); #else authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims, const char *require_args, oidc_authz_match_claim_fn_type match_claim_fn); #endif int oidc_oauth_return_www_authenticate(request_rec *r, const char *error, const char *error_description); // oidc_config.c #define OIDCPrivateKeyFiles "OIDCPrivateKeyFiles" #define OIDCRedirectURI "OIDCRedirectURI" #define OIDCDefaultURL "OIDCDefaultURL" #define OIDCCookieDomain "OIDCCookieDomain" #define OIDCClaimPrefix "OIDCClaimPrefix" #define OIDCRemoteUserClaim "OIDCRemoteUserClaim" #define OIDCOAuthRemoteUserClaim "OIDCOAuthRemoteUserClaim" #define OIDCSessionType "OIDCSessionType" #define OIDCMemCacheServers "OIDCMemCacheServers" #define OIDCCacheShmMax "OIDCCacheShmMax" #define OIDCCacheShmEntrySizeMax "OIDCCacheShmEntrySizeMax" #define OIDCRedisCacheServer "OIDCRedisCacheServer" #define OIDCCookiePath "OIDCCookiePath" #define OIDCInfoHook "OIDCInfoHook" #define OIDCWhiteListedClaims "OIDCWhiteListedClaims" void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr); void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD); void *oidc_create_dir_config(apr_pool_t *pool, char *path); void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD); void oidc_register_hooks(apr_pool_t *pool); char *oidc_cfg_dir_discover_url(request_rec *r); char *oidc_cfg_dir_cookie(request_rec *r); char *oidc_cfg_dir_cookie_path(request_rec *r); char *oidc_cfg_dir_authn_header(request_rec *r); apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r); apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r); apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r); apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r); char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key); int oidc_cfg_token_introspection_interval(request_rec *r); int oidc_cfg_dir_preserve_post(request_rec *r); apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r); apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r); int oidc_dir_cfg_unauth_action(request_rec *r); int oidc_dir_cfg_unautz_action(request_rec *r); char *oidc_dir_cfg_path_auth_request_params(request_rec *r); char *oidc_dir_cfg_path_scope(request_rec *r); oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg *cfg); int oidc_cfg_cache_encrypt(request_rec *r); int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r); const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type); const char *oidc_cfg_claim_prefix(request_rec *r); // oidc_util.c int oidc_strnenvcmp(const char *a, const char *b, int len); int oidc_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding); int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src); const char *oidc_get_current_url_host(request_rec *r); char *oidc_get_current_url(request_rec *r); const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *c); const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *c, oidc_provider_t *provider); char *oidc_url_encode(const request_rec *r, const char *str, const char *charsToEncode); char *oidc_normalize_header_name(const request_rec *r, const char *str); void oidc_util_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext); char *oidc_util_get_cookie(request_rec *r, const char *cookieName); apr_byte_t oidc_util_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key); apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key); apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url, json_t *data, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key); apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url); apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char* param); apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name, char **value); char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags); apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json); apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json); int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue); int oidc_util_html_send(request_rec *r, const char *title, const char *html_head, const char *on_load, const char *html_body, int status_code); char *oidc_util_escape_string(const request_rec *r, const char *str); char *oidc_util_unescape_string(const request_rec *r, const char *str); apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, char *data); apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table); apr_byte_t oidc_util_file_read(request_rec *r, const char *path, apr_pool_t *pool, char **result); apr_byte_t oidc_util_file_write(request_rec *r, const char *path, const char *data); apr_byte_t oidc_util_issuer_match(const char *a, const char *b); int oidc_util_html_send_error(request_rec *r, const char *html_template, const char *error, const char *description, int status_code); apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack, const char *needle); void oidc_util_set_app_info(request_rec *r, const char *s_key, const char *s_value, const char *claim_prefix, apr_byte_t as_header, apr_byte_t as_env_var); void oidc_util_set_app_infos(request_rec *r, const json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, apr_byte_t as_header, apr_byte_t as_env_var); apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str); apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b); apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match); apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value); apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value); apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value); char *oidc_util_html_escape(apr_pool_t *pool, const char *input); void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params); apr_hash_t * oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2); apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str); apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str); apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst); int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain); apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input, char **output); apr_byte_t oidc_util_jwt_create(request_rec *r, const char *secret, json_t *payload, char **compact_encoded_jwt); apr_byte_t oidc_util_jwt_verify(request_rec *r, const char *secret, const char *compact_encoded_jwt, json_t **result); char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName, int cookie_chunk_size); void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext); apr_byte_t oidc_util_create_symmetric_key(request_rec *r, const char *client_secret, unsigned int r_key_len, const char *hash_algo, apr_byte_t set_kid, oidc_jwk_t **jwk); apr_hash_t * oidc_util_merge_symmetric_key(apr_pool_t *pool, apr_hash_t *private_keys, oidc_jwk_t *jwk); const char *oidc_util_get_provided_token_binding_id(const request_rec *r); char *oidc_util_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params); char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename); /* HTTP header constants */ #define OIDC_HTTP_HDR_COOKIE "Cookie" #define OIDC_HTTP_HDR_SET_COOKIE "Set-Cookie" #define OIDC_HTTP_HDR_USER_AGENT "User-Agent" #define OIDC_HTTP_HDR_X_FORWARDED_FOR "X-Forwarded-For" #define OIDC_HTTP_HDR_CONTENT_TYPE "Content-Type" #define OIDC_HTTP_HDR_X_REQUESTED_WITH "X-Requested-With" #define OIDC_HTTP_HDR_ACCEPT "Accept" #define OIDC_HTTP_HDR_AUTHORIZATION "Authorization" #define OIDC_HTTP_HDR_X_FORWARDED_PROTO "X-Forwarded-Proto" #define OIDC_HTTP_HDR_X_FORWARDED_PORT "X-Forwarded-Port" #define OIDC_HTTP_HDR_X_FORWARDED_HOST "X-Forwarded-Host" #define OIDC_HTTP_HDR_HOST "Host" #define OIDC_HTTP_HDR_LOCATION "Location" #define OIDC_HTTP_HDR_CACHE_CONTROL "Cache-Control" #define OIDC_HTTP_HDR_PRAGMA "Pragma" #define OIDC_HTTP_HDR_P3P "P3P" #define OIDC_HTTP_HDR_EXPIRES "Expires" #define OIDC_HTTP_HDR_X_FRAME_OPTIONS "X-Frame-Options" #define OIDC_HTTP_HDR_WWW_AUTHENTICATE "WWW-Authenticate" #define OIDC_HTTP_HDR_INCLUDE_REFERRED_TOKEN_BINDING_ID "Include-Referred-Token-Binding-ID" #define OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST "XMLHttpRequest" void oidc_util_hdr_in_set(const request_rec *r, const char *name, const char *value); const char *oidc_util_hdr_in_cookie_get(const request_rec *r); void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value); const char *oidc_util_hdr_in_user_agent_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r); const char *oidc_util_hdr_in_content_type_get(const request_rec *r); const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r); const char *oidc_util_hdr_in_accept_get(const request_rec *r); const char *oidc_util_hdr_in_authorization_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r); const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r); const char *oidc_util_hdr_in_host_get(const request_rec *r); void oidc_util_hdr_out_location_set(const request_rec *r, const char *value); const char *oidc_util_hdr_out_location_get(const request_rec *r); void oidc_util_hdr_err_out_add(const request_rec *r, const char *name, const char *value); // oidc_metadata.c apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response); apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg *cfg, json_t *j_provider, oidc_provider_t *provider); apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg, apr_array_header_t **arr); apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg *cfg, const char *selected, oidc_provider_t **provider, apr_byte_t allow_discovery); apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *cfg, const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks, apr_byte_t *refresh); // oidc_session.c typedef struct { char uuid[APR_UUID_FORMATTED_LENGTH + 1]; /* unique id */ const char *remote_user; /* user who owns this particular session */ json_t *state; /* the state for this session, encoded in a JSON object */ apr_time_t expiry; /* if > 0, the time of expiry of this session */ } oidc_session_t; apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **z); apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key, const char **value); apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value); apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first_time); apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z); apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *userinfo_jwt); const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z); void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims); const char * oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z); json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z); void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, const char *idtoken_claims); const char * oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z); json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z); void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token); const char * oidc_session_get_idtoken(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token); const char * oidc_session_get_access_token(request_rec *r, oidc_session_t *z); void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in); const char * oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z); void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token); const char * oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z); void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires); apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z); void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain); const char * oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z); void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z); apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, oidc_session_t *z); void oidc_session_reset_access_token_last_refresh(request_rec *r, oidc_session_t *z); apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, oidc_session_t *z); void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state); const char * oidc_session_get_request_state(request_rec *r, oidc_session_t *z); void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url); const char * oidc_session_get_original_url(request_rec *r, oidc_session_t *z); void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state); const char * oidc_session_get_session_state(request_rec *r, oidc_session_t *z); void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer); const char * oidc_session_get_issuer(request_rec *r, oidc_session_t *z); void oidc_session_set_client_id(request_rec *r, oidc_session_t *z, const char *client_id); const char * oidc_session_get_client_id(request_rec *r, oidc_session_t *z); void oidc_session_set_check_session_iframe(request_rec *r, oidc_session_t *z, const char *check_session_iframe); const char * oidc_session_get_check_session_iframe(request_rec *r, oidc_session_t *z); void oidc_session_set_logout_endpoint(request_rec *r, oidc_session_t *z, const char *logout_endpoint); const char * oidc_session_get_logout_endpoint(request_rec *r, oidc_session_t *z); #endif /* MOD_AUTH_OPENIDC_H_ */ mod_auth_openidc-2.3.3/src/jose.h0000644000076500000240000002205013200665522016556 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JSON Object Signing and Encryption * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #ifndef MOD_AUTH_OPENIDC_JOSE_H_ #define MOD_AUTH_OPENIDC_JOSE_H_ #include #include "apr_pools.h" #include "apr_tables.h" #include "apr_hash.h" #include "apr_strings.h" #include "jansson.h" #include "cjose/cjose.h" #define OIDC_JOSE_ALG_SHA256 "sha256" /* indicate support for OpenSSL version dependent features */ #define OIDC_JOSE_EC_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f #define OIDC_JOSE_GCM_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f /* error message element sizes */ #define OIDC_JOSE_ERROR_TEXT_LENGTH 200 #define OIDC_JOSE_ERROR_SOURCE_LENGTH 80 #define OIDC_JOSE_ERROR_FUNCTION_LENGTH 80 /* struct for returning errors to the caller */ typedef struct { char source[OIDC_JOSE_ERROR_SOURCE_LENGTH]; int line; char function[OIDC_JOSE_ERROR_FUNCTION_LENGTH]; char text[OIDC_JOSE_ERROR_TEXT_LENGTH]; } oidc_jose_error_t; /* * error handling functions */ void _oidc_jose_error_set(oidc_jose_error_t *, const char *, const int, const char *, const char *msg, ...); #define oidc_jose_error(err, msg, ...) _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, msg, ##__VA_ARGS__) #define oidc_jose_error_openssl(err, msg, ...) _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, "%s() failed: %s", msg, ERR_error_string(ERR_get_error(), NULL), ##__VA_ARGS__) #define oidc_jose_e2s(pool, err) apr_psprintf(pool, "[%s:%d: %s]: %s\n", err.source, err.line, err.function, err.text) #define oidc_cjose_e2s(pool, cjose_err) apr_psprintf(pool, "%s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line) /* * helper functions */ /* helpers to find out about the supported ala/enc algorithms */ apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool); apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg); apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool); apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg); apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool); apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc); /* hash helpers */ apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, unsigned int *hash_len, oidc_jose_error_t *err); int oidc_jose_hash_length(const char *alg); apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, unsigned int input_len, unsigned char **output, unsigned int *output_len, oidc_jose_error_t *err); /* return a string claim value from a JSON object */ apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json, const char *claim_name, apr_byte_t is_mandatory, char **result, oidc_jose_error_t *err); /* a parsed JWK/JWT JSON object */ typedef struct oidc_jose_json_t { /* parsed JSON struct representation */ json_t *json; /* string representation */ char *str; } oidc_jose_json_t; /* * JSON Web Key handling */ /* parsed JWK */ typedef struct oidc_jwk_t { /* key type */ int kty; /* key identifier */ char *kid; /* cjose JWK structure */ cjose_jwk_t *cjose_jwk; } oidc_jwk_t; /* decrypt a JWT */ apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json, apr_hash_t *keys, char **s_json, oidc_jose_error_t *err, apr_byte_t import_must_succeed); /* parse a JSON string to a JWK struct */ oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const char *s_json, oidc_jose_error_t *err); /* parse a JSON object in to a JWK struct */ apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* convert a JWK struct to a JSON string */ apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *err); /* destroy resources allocated for a JWK struct */ void oidc_jwk_destroy(oidc_jwk_t *jwk); /* destroy a list of JWKs structs */ void oidc_jwk_list_destroy(apr_pool_t *pool, apr_hash_t *key); /* create an "oct" symmetric JWK */ oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *kid, const unsigned char *key, unsigned int key_len, apr_byte_t set_kid, oidc_jose_error_t *err); /* parse an X.509 PEM formatted certificate file with an RSA public key to a JWK struct */ apr_byte_t oidc_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* parse an X.509 PEM formatted RSA private key file to a JWK */ apr_byte_t oidc_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err); /* * JSON Web Token handling */ /* represents NULL timestamp */ #define OIDC_JWT_CLAIM_TIME_EMPTY -1 /* a parsed JWT header */ typedef struct oidc_jwt_hdr_t { /* parsed header value */ oidc_jose_json_t value; /* JWT "alg" claim value; signing algorithm */ char *alg; /* JWT "kid" claim value; key identifier */ char *kid; /* JWT "enc" claim value; encryption algorithm */ char *enc; } oidc_jwt_hdr_t; /* parsed JWT payload */ typedef struct oidc_jwt_payload_t { /* parsed payload value */ oidc_jose_json_t value; /* JWT "iss" claim value; JWT issuer */ char *iss; /* JWT "sub" claim value; subject/principal */ char *sub; /* parsed JWT "exp" claim value; token expiry */ double exp; /* parsed JWT "iat" claim value; issued-at timestamp */ double iat; } oidc_jwt_payload_t; /* parsed JWT */ typedef struct oidc_jwt_t { /* parsed JWT header */ oidc_jwt_hdr_t header; /* parsed JWT payload */ oidc_jwt_payload_t payload; /* cjose JWS structure */ cjose_jws_t *cjose_jws; } oidc_jwt_t; /* parse a string into a JSON Web Token struct and (optionally) decrypt it */ apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *s_json, oidc_jwt_t **j_jwt, apr_hash_t *keys, oidc_jose_error_t *err); /* sign a JWT with a JWK */ apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk, oidc_jose_error_t *err); /* verify a JWT a key in a list of JWKs */ apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err); /* perform compact serialization on a JWT and return the resulting string */ char *oidc_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err); /* encrypt JWT */ apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk, const char *payload, char **serialized, oidc_jose_error_t *err); /* create a new JWT */ oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload); /* destroy resources allocated for JWT */ void oidc_jwt_destroy(oidc_jwt_t *); /* get a header value from a JWT */ const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key); /* return the key type of a JWT */ int oidc_jwt_alg2kty(oidc_jwt_t *jwt); /* return the key size for an algorithm */ unsigned int oidc_alg2keysize(const char *alg); #endif /* MOD_AUTH_OPENIDC_JOSE_H_ */ mod_auth_openidc-2.3.3/src/parse.h0000644000076500000240000001571313203320522016727 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Validation and parsing of configuration values. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #ifndef MOD_AUTH_OPENIDC_PARSE_H_ #define MOD_AUTH_OPENIDC_PARSE_H_ #include "apr_pools.h" #define OIDC_CONFIG_STRING_UNSET "_UNSET_" #define OIDC_CONFIG_STRING_EMPTY "" #define OIDC_CONFIG_POS_INT_UNSET -1 #define OIDC_CLAIM_FORMAT_RELATIVE "relative" #define OIDC_CLAIM_FORMAT_ABSOLUTE "absolute" #define OIDC_CLAIM_REQUIRED_MANDATORY "mandatory" #define OIDC_CLAIM_REQUIRED_OPTIONAL "optional" #define OIDC_PKCE_METHOD_PLAIN "plain" #define OIDC_PKCE_METHOD_S256 "S256" #define OIDC_PKCE_METHOD_REFERRED_TB "referred_tb" const char *oidc_valid_url(apr_pool_t *pool, const char *arg, const char *scheme); const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg); const char *oidc_valid_dir(apr_pool_t *pool, const char *arg); const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg); const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool,const char *arg); const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg); const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg); const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg); const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg); const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg); const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg); const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v); const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v); const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v); const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v); const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg); const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg); const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg); const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value); const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg, oidc_cache_t **type); const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg, int *type, int *persistent); const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, apr_byte_t triplet); const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value); const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value); const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options); const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v); const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required); const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg, int *in_headers, int *in_env_vars); const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg, int *action); const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg, int *action); const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_hash_t **hook_data); const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *int_value); const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v); const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method); typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int); typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *); const char *oidc_valid_string_in_array(apr_pool_t *pool, json_t *json, const char *key, oidc_valid_function_t valid_function, char **value, apr_byte_t optional); #endif /* MOD_AUTH_OPENIDC_PARSE_H_ */ mod_auth_openidc-2.3.3/src/cache/cache.h0000644000076500000240000001404513200625410017721 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * mem_cache-like interface and semantics (string keys/values) using a storage backend * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu */ #ifndef _MOD_AUTH_OPENIDC_CACHE_H_ #define _MOD_AUTH_OPENIDC_CACHE_H_ #include "apr_global_mutex.h" #include "apr_shm.h" typedef void * (*oidc_cache_cfg_create)(apr_pool_t *pool); typedef int (*oidc_cache_post_config_function)(server_rec *s); typedef int (*oidc_cache_child_init_function)(apr_pool_t *p, server_rec *s); typedef apr_byte_t (*oidc_cache_get_function)(request_rec *r, const char *section, const char *key, const char **value); typedef apr_byte_t (*oidc_cache_set_function)(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry); typedef int (*oidc_cache_destroy_function)(server_rec *s); typedef struct oidc_cache_t { const char *name; int encrypt_by_default; oidc_cache_post_config_function post_config; oidc_cache_child_init_function child_init; oidc_cache_get_function get; oidc_cache_set_function set; oidc_cache_destroy_function destroy; } oidc_cache_t; typedef struct oidc_cache_mutex_t { apr_global_mutex_t *mutex; char *mutex_filename; apr_shm_t *shm; int *sema; } oidc_cache_mutex_t; oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool); apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m, const char *type); apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_lock(request_rec *r, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_unlock(request_rec *r, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m); apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key, char **value); apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key, const char *value, apr_time_t expiry); #define OIDC_CACHE_SECTION_SESSION "s" #define OIDC_CACHE_SECTION_NONCE "n" #define OIDC_CACHE_SECTION_JWKS "j" #define OIDC_CACHE_SECTION_ACCESS_TOKEN "a" #define OIDC_CACHE_SECTION_PROVIDER "p" #define OIDC_CACHE_SECTION_JTI "t" #define OIDC_CACHE_SECTION_REQUEST_URI "r" #define oidc_cache_get_session(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_SESSION, key, value) #define oidc_cache_get_nonce(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_NONCE, key, value) #define oidc_cache_get_jwks(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JWKS, key, value) #define oidc_cache_get_access_token(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value) #define oidc_cache_get_provider(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_PROVIDER, key, value) #define oidc_cache_get_jti(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JTI, key, value) #define oidc_cache_get_request_uri(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value) #define oidc_cache_set_session(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_SESSION, key, value, expiry) #define oidc_cache_set_nonce(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_NONCE, key, value, expiry) #define oidc_cache_set_jwks(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JWKS, key, value, expiry) #define oidc_cache_set_access_token(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value, expiry) #define oidc_cache_set_provider(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_PROVIDER, key, value, expiry) #define oidc_cache_set_jti(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JTI, key, value, expiry) #define oidc_cache_set_request_uri(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value, expiry) extern oidc_cache_t oidc_cache_file; extern oidc_cache_t oidc_cache_memcache; extern oidc_cache_t oidc_cache_shm; #ifdef USE_LIBHIREDIS extern oidc_cache_t oidc_cache_redis; #endif #endif /* _MOD_AUTH_OPENIDC_CACHE_H_ */ mod_auth_openidc-2.3.3/src/pcre_subst.h0000644000076500000240000000270413200625410017763 0ustar hzandbeltstaff/************************************************* * PCRE string replacement * *************************************************/ /* PCRE is a library of functions to support regular expressions whose syntax and semantics are as close as possible to those of the Perl 5 language. pcre_subst is a wrapper around pcre_exec designed to make it easier to perform PERL style replacements with PCRE. Written by: Bert Driehuis Copyright (c) 2000 Bert Driehuis ----------------------------------------------------------------------------- Permission is granted to anyone to use this software for any purpose on any computer system, and to redistribute it freely, subject to the following restrictions: 1. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 2. The origin of this software must not be misrepresented, either by explicit claim or by omission. 3. Altered versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. If PCRE is embedded in any software that is released under the GNU General Purpose Licence (GPL), then the terms of that licence shall supersede any condition above with which it is incompatible. */ char *pcre_subst(const pcre *, const pcre_extra *, const char *, int, int, int, const char *); mod_auth_openidc-2.3.3/test/test.c0000755000076500000240000015366113200625410016770 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu * **************************************************************************/ #include #include #include #include #include "apr.h" #include "apr_errno.h" #include "apr_general.h" #include "apr_time.h" #include "apr_base64.h" #include "mod_auth_openidc.h" extern module AP_MODULE_DECLARE_DATA auth_openidc_module; static int test_nr_run = 0; static char TST_ERR_MSG[512]; static int TST_RC; #define TST_FORMAT(fmt) \ " # %s: error in %s: result \"" fmt "\" != expected \"" fmt "\"" #define TST_ASSERT(message, expression) \ TST_RC = (expression); \ if (!TST_RC) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%d"), __FUNCTION__, message, TST_RC, 1); \ return TST_ERR_MSG; \ } #define TST_ASSERT_ERR(message, expression, pool, err) \ TST_RC = (expression); \ if (!TST_RC) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, oidc_jose_e2s(pool, err)); \ return TST_ERR_MSG; \ } #define TST_ASSERT_CJOSE_ERR(message, expression, pool, cjose_err) \ TST_RC = (expression); \ if (!TST_RC) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, oidc_cjose_e2s(pool, cjose_err)); \ return TST_ERR_MSG; \ } #define TST_ASSERT_STR(message, result, expected) \ TST_RC = (result && expected) ? (apr_strnatcmp(result, expected) != 0) : ((result != NULL) || (expected != NULL)); \ if (TST_RC) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", expected ? expected : "(null)"); \ return TST_ERR_MSG; \ } #define TST_ASSERT_STRN(message, result, expected, len) \ TST_RC = (result && expected) ? (strncmp(result, expected, len) != 0) : ((result != NULL) || (expected != NULL)); \ if (TST_RC) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", expected ? expected : "(null)"); \ return TST_ERR_MSG; \ } #define TST_ASSERT_LONG(message, result, expected) \ if (result != expected) { \ sprintf(TST_ERR_MSG, TST_FORMAT("%ld"), __FUNCTION__, message, result, expected); \ return TST_ERR_MSG; \ } #define TST_RUN(test, pool) message = test(pool); test_nr_run++; if (message) return message; static char *_jwk_parse(apr_pool_t *pool, const char *s, oidc_jwk_t **jwk, oidc_jose_error_t *err) { oidc_jwk_t *k = oidc_jwk_parse(pool, s, err); TST_ASSERT_ERR("oidc_jwk_parse", k != NULL, pool, (*err)); *jwk = k; return 0; } static char *test_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 3.1. Example JWT char *s = apr_pstrdup(pool, "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"); oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "HS256"); TST_ASSERT_STR("header.enc", jwt->header.enc, NULL); TST_ASSERT_STR("header.kid", jwt->header.kid, NULL); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk; const char * k = "{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); s[5] = OIDC_CHAR_DOT; TST_ASSERT_ERR("corrupted header (1) oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); s[0] = '\0'; TST_ASSERT_ERR("corrupted header (2) oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); return 0; } #if (APR_JWS_EC_SUPPORT) static char *test_jwt_verify_ec(apr_pool_t *pool) { // { // "sub": "joe", // "aud": "ac_oic_client", // "jti": "oDWivWPJB47zkjOm2cygDv", // "iss": "https://localhost:9031", // "iat": 1467997207, // "exp": 1467997507, // "nonce": "WLxmv5StYyUk9JlWI8SaXTLPkGZ0Vs8aSTdj_VQ6rao" // } char *s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg"); oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse (ec0)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); char *s_key = "{" "\"kty\": \"EC\"," "\"kid\": \"f6qtj\"," "\"use\": \"sig\"," "\"x\": \"iARwFlN3B3xa8Zn_O-CVfqry68tXIhO9DckKo1yrNg0\"," "\"y\": \"583S_mPS7YVZtLCjx2O69G_JzQPnMxjieOli-9cc_6Q\"," "\"crv\": \"P-256\"" "}"; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "f6qtj", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify (ec0)", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7EG"); jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (ec1)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (ec1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJHdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg"); jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (ec2)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (ec2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return 0; } #endif static char *test_jwt_verify_rsa(apr_pool_t *pool) { /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"; oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); char *s_key = "{" "\"kty\": \"RSA\"," "\"n\": \"3lDyn_ZvG32Pw5kYbRuVxHsPfe9Xt8s9vOXnt8z7_T-hZZvealNhCxz9VEwTJ7TsZ9CLi5c30FjoEJYFkKddLAdxKo0oOXWc_AWrQvPwht9a-o6dX2fL_9CmXW1hGHXMH0qiLMrFqMSzZeh-GUY6F1woE_eKsAo6LOhP8X77FlEQT2Eu71wu8KC4B3sH_9QTco50KNw14-bRY5j2V2TZelvsXJnvrN4lXtEVYWFkREKeXzMH8DhDyZzh0NcHa7dFBa7rDusyfIHjuP6uAju_Ao6hhdOGjlKePMVtfusWBAI7MWDChLTqiCTvlZnCpkpTTh5m-i7TbE1TwmdbLceq1w\"," "\"e\": \"AQAB\"" "}"; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwt_destroy(jwt); jwt = NULL; s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE1MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"; TST_ASSERT_ERR("oidc_jwt_parse (rsa1)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); jwt = NULL; s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEa"; TST_ASSERT_ERR("oidc_jwt_parse (rsa2)", oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE, pool, err); oidc_jwt_destroy(jwt); oidc_jwk_destroy(jwk); return 0; } static char *test_jwt_sign_verify(apr_pool_t *pool) { oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; char *cser = NULL; oidc_jose_error_t err; char *s_key = "{" "\"kty\" : \"RSA\"," "\"n\": \"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63PpGRnfSsX3dOweh8BXd2AJ3mxvcE4z9xH--tA5EaOGI7IVF0Ip_i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_3Dvt2CKDV4LTPPJe9KI2_LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tddhg4cle-DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-YCRq6C61je_mzFhuF4pczw\"," "\"e\": \"AQAB\"," "\"d\": \"qvxW_e8DoCnUn8uLHUKTsS1hkXqFI4SHZYFl0jeG6m7ncwHolxvR3ljg9tyGHuFX55sizu7MMuHgrkyxbUWgv0ILD2qmvOiHOTDfuRjP-58JRW0UfqiVQTSgl3jCNRW9WdoxZU-ptD6_NGSVNLwAJsUB2r4mm4PctaMuHINKjp_TnuD-5vfi9Tj88hbqvX_0j8T62ZaLRdERb1KGDM_8bnqQpnLZ0MZQnpLQ8cKIcjj7p0II6pzvqgdO1RqfYx7qG0cbcIRh26rnB9X4rp5BrbvDzKe6NOqacZUcNUmbPzI01-hiT0HgJvV592CBOxt2T31ltQ4wCEdzhQeT3n9_wQ\"" "}"; apr_hash_t *keys = apr_hash_make(pool); TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); jwt = oidc_jwt_new(pool, TRUE, TRUE); json_object_set_new(jwt->payload.value.json, "iss", json_string("https://example.org")); json_object_set_new(jwt->payload.value.json, "sub", json_string("https://example.org")); json_object_set_new(jwt->payload.value.json, "aud", json_string("sample_client")); json_object_set_new(jwt->payload.value.json, "exp", json_integer(apr_time_sec(apr_time_now()) + 60)); json_object_set_new(jwt->payload.value.json, "iat", json_integer(apr_time_sec(apr_time_now()))); jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_RS256); TST_ASSERT_ERR("oidc_jwt_sign (rsa)", oidc_jwt_sign(pool, jwt, jwk, &err), pool, err); cser = oidc_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_jwt_serialize (rsa)", cser != NULL, pool, err); oidc_jwt_t *rsa_jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &rsa_jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa)", oidc_jwt_verify(pool, rsa_jwt, keys, &err), pool, err); oidc_jwt_destroy(rsa_jwt); oidc_jwk_destroy(jwk); const char *secret = "my_secret4321"; jwk = oidc_jwk_create_symmetric_key(pool, NULL, (const unsigned char *)secret, strlen(secret), FALSE, &err); TST_ASSERT_ERR("oidc_jwk_create_symmetric_key", jwk != NULL, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); jwt->header.alg = apr_pstrdup(pool, "HS256"); TST_ASSERT_ERR("oidc_jwt_sign (hmac)", oidc_jwt_sign(pool, jwt, jwk, &err), pool, err); cser = oidc_jwt_serialize(pool, jwt, &err); TST_ASSERT_ERR("oidc_jwt_serialize (hmac)", cser != NULL, pool, err); oidc_jwt_t *hmac_jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &hmac_jwt, NULL, &err), pool, err); TST_ASSERT_ERR("oidc_jwt_verify (rsa)", oidc_jwt_verify(pool, hmac_jwt, keys, &err), pool, err); oidc_jwt_destroy(hmac_jwt); oidc_jwk_destroy(jwk); oidc_jwt_destroy(jwt); return 0; } static char *test_plaintext_jwt_parse(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20 // 6.1. Example Plaintext JWT char *s = apr_pstrdup(pool, "eyJhbGciOiJub25lIn0" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" "."); oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); TST_ASSERT_STR("header.alg", jwt->header.alg, "none"); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); oidc_jwt_destroy(jwt); return 0; } static char *test_jwt_get_string(apr_pool_t *pool) { //oidc_jose_get_string const char *s = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err), pool, err); char *dst = NULL; TST_ASSERT("oidc_jose_get_string (1a)", oidc_jose_get_string(pool, jwt->header.value.json, "typ", TRUE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (1b)", dst, "JWT"); dst = NULL; TST_ASSERT("oidc_jose_get_string (2a)", oidc_jose_get_string(pool, jwt->header.value.json, "alg", TRUE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (2b)", dst, "HS256"); dst = NULL; TST_ASSERT("oidc_jose_get_string (3a)", oidc_jose_get_string(pool, jwt->header.value.json, "dummy", FALSE, &dst, &err)); TST_ASSERT_STR("oidc_jose_get_string (3b)", dst, NULL); oidc_jwt_destroy(jwt); return 0; } static char *test_jwk_parse_json(apr_pool_t *pool) { const char *s = "{\"kty\":\"EC\",\"use\":\"sig\"," "\"kid\":\"the key\"," "\"x\":\"amuk6RkDZi-48mKrzgBN_zUZ_9qupIwTZHJjM03qL-4\"," "\"y\":\"ZOESj6_dpPiZZR-fJ-XVszQta28Cjgti7JudooQJ0co\",\"crv\":\"P-256\"}"; oidc_jose_error_t err; oidc_jwk_t *jwk; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse (1)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3 // A.3. Example Symmetric Keys #1 s = "{" "\"kty\":\"oct\"," "\"alg\":\"A128KW\"," "\"k\" :\"GawgguFyGrWKav7AX4VKUg\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #1)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); //TST_ASSERT_LONG("#1 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT); //TST_ASSERT_LONG("#1 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 16L); // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3 // A.3. Example Symmetric Keys #2 s = "{" "\"kty\":\"oct\"," "\"k\" :\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"," "\"kid\":\"HMAC key used in JWS A.1 example\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #2)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); //TST_ASSERT_LONG("#2 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT); //TST_ASSERT_LONG("#2 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 64L); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.1 // 3.1. EC Public Key s = "{" "\"kty\": \"EC\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"crv\": \"P-521\"," "\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\"," "\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.1, EC Public Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.2 // 3.2. EC Private Key s = "{" "\"kty\": \"EC\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"crv\": \"P-521\"," "\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\"," "\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\"," "\"d\": \"AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.2, EC Private Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.3 // 3.3. RSA Public Key s = "{" "\"kty\": \"RSA\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw\"," "\"e\": \"AQAB\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.3, RSA Public Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); // https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.4 // 3.4. RSA Private Key s = "{" "\"kty\": \"RSA\"," "\"kid\": \"bilbo.baggins@hobbiton.example\"," "\"use\": \"sig\"," "\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw\"," "\"e\": \"AQAB\"," "\"d\": \"bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e" "iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld" "Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b" "MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU" "6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj" "d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc" "OpBrQzwQ\"," "\"p\": \"3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR" "aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG" "peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8" "bUq0k\"," "\"q\": \"uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT" "8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an" "V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0" "s7pFc\"," "\"dp\": \"B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q" "1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn" "-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX" "59ehik\"," "\"dq\": \"CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr" "AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK" "bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK" "T1cYF8\"," "\"qi\": \"3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N" "ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh" "jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP" "z8aaI4\"" "}"; jwk = NULL; TST_ASSERT_ERR( "oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.4, RSA Private Key)", _jwk_parse(pool, s, &jwk, &err) == 0, pool, err); oidc_jwk_destroy(jwk); return 0; } static char *test_plaintext_decrypt(apr_pool_t *pool) { // from http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-30 // A.2. Example JWE using RSAES-PKCS1-V1_5 and AES_128_CBC_HMAC_SHA_256 char *s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0" ".UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" ".AxY8DCtDaGlsbGljb3RoZQ" ".KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY" ".9hH0vgRfYgPnAHOd8stkvw"); char * k = "{\"kty\":\"RSA\"," "\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," "\"e\":\"AQAB\"," "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "Live long and prosper.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); return 0; } static char *test_plaintext_decrypt2(apr_pool_t *pool) { // http://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-5.1.5 // 5.1. Key Encryption using RSA v1.5 and AES-HMAC-SHA2 char *s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm" "V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0" "." "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF" "vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G" "Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG" "TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl" "zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh" "MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw" "." "bbd5sTkYwhAIqfHsx8DayA" "." "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r" "aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O" "WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV" "yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0" "zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2" "O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW" "i7lzA6BP430m" "." "kvKuFBXHe5mQr4lqgobAUg"); char * k = "{" "\"kty\": \"RSA\"," "\"kid\": \"frodo.baggins@hobbiton.example\"," "\"use\": \"enc\"," "\"n\": \"maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegT" "HVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx" "6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5U" "NwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4c" "R5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oy" "pBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYA" "VotGlvMQ\"," "\"e\": \"AQAB\"," "\"d\": \"Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wy" "bQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO" "5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6" "Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP" "1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PN" "miuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2v" "pzj85bQQ\"," "\"p\": \"2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaE" "oekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH" "7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ" "2VFmU\"," "\"q\": \"te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_V" "F099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb" "9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8" "d6Et0\"," "\"dp\": \"UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTH" "QmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JV" "RDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsf" "lo0rYU\"," "\"dq\": \"iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9Mb" "pFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87A" "CfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14" "TkXlHE\"," "\"qi\": \"kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZ" "lXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7" "Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx" "2bQ_mM\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "frodo.baggins@hobbiton.example", APR_HASH_KEY_STRING, jwk); cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "You can trust us to stick with you through thick and " "thin\u2013to the bitter end. And you can trust us to " "keep any secret of yours\u2013closer than you keep it " "yourself. But you cannot trust us to let you face trouble " "alone, and go off without a word. We are your friends, Frodo.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); return 0; } static char *test_plaintext_decrypt_symmetric(apr_pool_t *pool) { oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk; // http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-40#appendix-A.3 // A.3. Example JWE using AES Key Wrap and AES_128_CBC_HMAC_SHA_256 const char * k = "{\"kty\":\"oct\", \"k\":\"GawgguFyGrWKav7AX4VKUg\"}"; jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); const char *s = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." "AxY8DCtDaGlsbGljb3RoZQ." "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." "U0m_YmjN04DJvceFICbCVQ"; cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "Live long and prosper.", content_len); cjose_get_dealloc()(decrypted); oidc_jwk_destroy(jwk); cjose_jwe_release(jwe); return 0; } static char *test_jwt_decrypt(apr_pool_t *pool) { // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#appendix-A.1 // A.2. Example Nested JWT char * s = apr_pstrdup(pool, "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldU" "In0." "g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_M" "qewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYE" "b9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvh" "DuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4VZGGlxRGPLRHvolVLEHx6D" "YyLpw30Ay9R6d68YCLi9FYTq3hIXPK_-dmPlOUlKvPr1GgJzRoeC9G5qCvdcHWsq" "JGTO_z3Wfo5zsqwkxruxwA." "UmVkbW9uZCBXQSA5ODA1Mg." "VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTB" "BLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT" "-FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10" "l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6ULmkV-uLC4FUiyKECK4e3WZY" "Kw1bpgIqGYsw2v_grHjszJZ-_I5uM-9RA8ycX9KqPRp9gc6pXmoU_-27ATs9XCvr" "ZXUtK2902AUzqpeEUJYjWWxSNsS-r1TJ1I-FMJ4XyAiGrfmo9hQPcNBYxPz3GQb2" "8Y5CLSQfNgKSGt0A4isp1hBUXBHAndgtcslt7ZoQJaKe_nNJgNliWtWpJ_ebuOpE" "l8jdhehdccnRMIwAmU1n7SPkmhIl1HlSOpvcvDfhUN5wuqU955vOBvfkBOh5A11U" "zBuo2WlgZ6hYi9-e3w29bR0C2-pp3jbqxEDw3iWaf2dc5b-LnR0FEYXvI_tYk5rd" "_J9N0mg0tQ6RbpxNEMNoA9QWk5lgdPvbh9BaO195abQ." "AVO9iT5AV4CzvDJCdhSFlQ"); char * ek = "{\"kty\":\"RSA\"," "\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\"," "\"e\":\"AQAB\"," "\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\"" "}"; char * sk = "{" "\"kty\":\"RSA\"," "\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\"," "\"e\":\"AQAB\"," "\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"," "\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\"," "\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\"," "\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\"," "\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\"," "\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk_e = NULL; oidc_jwk_t *jwk_s = NULL; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwk_parse (encryption key)", _jwk_parse(pool, ek, &jwk_e, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_e); TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, keys, &err), pool, err); oidc_jwk_destroy(jwk_e); TST_ASSERT_ERR("oidc_jwk_parse (signing key)", _jwk_parse(pool, sk, &jwk_s, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_s); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err), pool, err); oidc_jwk_destroy(jwk_s); TST_ASSERT_STR("header.alg", jwt->header.alg, CJOSE_HDR_ALG_RS256); TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe"); TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L); oidc_jwt_destroy(jwt); return 0; } #if (OPENSSL_VERSION_NUMBER >= 0x1000100f) static char *test_jwt_decrypt_gcm(apr_pool_t *pool) { // https://tools.ietf.org/html/rfc7516#appendix-A.1 // A.1. Example JWE using RSAES-OAEP and AES GCM char * s = apr_pstrdup(pool, "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV" "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8" "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi" "6UklfCpIMfIjf7iGdXKHzg." "48V1_ALb6US04U3b." "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji" "SdiwkIr3ajwQzaBtQD_A." "XFBoMYUZodetZdvTiFvSkQ"); char * k = "{\"kty\":\"RSA\"," "\"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\"," "\"e\":\"AQAB\"," "\"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\"," "\"p\":\"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-" "SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf" "fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0\"," "\"q\":\"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm" "UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX" "IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc\"," "\"dp\":\"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL" "hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827" "rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE\"," "\"dq\":\"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj" "ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB" "UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis\"," "\"qi\":\"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7" "AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3" "eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY\"" "}"; oidc_jose_error_t err; apr_hash_t *keys = apr_hash_make(pool); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0, pool, err); apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk); cjose_err cjose_err; cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err); size_t content_len = 0; uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err); TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err); TST_ASSERT_STRN("decrypted", (const char *)decrypted, "The true sign of intelligence is not knowledge but imagination.", content_len); cjose_get_dealloc()(decrypted); cjose_jwe_release(jwe); oidc_jwk_destroy(jwk); return 0; } #endif static char *test_proto_validate_access_token(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#id_token-tokenExample // A.3 Example using response_type=id_token token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiYXRfaGFzaCI6ICI3N1FtVVB0alBmeld0RjJBbnBLOVJ" "RIgp9.F9gRev0Dt2tKcrBkHy72cmRqnLdzw9FLCCSebV7mWs7o_sv2O5s6zM" "ky2kmhHTVx9HmdvNnx9GaZ8XMYRFeYk8L5NZ7aYlA5W56nsG1iWOou_-gji0" "ibWIuuf4Owaho3YSoi7EvsTuLFz6tq-dLyz0dKABMDsiCmJ5wqkPUDTE3QTX" "jzbUmOzUDli-gCh5QPuZAq0cNW3pf_2n4zpvTYtbmj12cVcxGIMZby7TMWES" "RjQ9_o3jvhVNcCGcE0KAQXejhA1ocJhNEvQNqMFGlBb6_0RxxKjDZ-Oa329e" "GDidOvvp0h5hoES4a8IuGKS7NOcpp-aFwp0qVMDLI-Xnm-Pg"; oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, &err), r->pool, err); const char *access_token = "jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y"; TST_ASSERT("oidc_proto_validate_access_token", oidc_proto_validate_access_token(r, NULL, jwt, "id_token token", access_token)); oidc_jwt_destroy(jwt); return 0; } static char *test_proto_validate_code(request_rec *r) { // from http://openid.net/specs/openid-connect-core-1_0.html#code-id_tokenExample // A.4 Example using response_type=code id_token const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml" "zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ" "4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA" "ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE" "zMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEE" "iCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcN" "egx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp" "_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWh" "sPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL" "7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_" "gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ"; oidc_jose_error_t err; oidc_jwt_t *jwt = NULL; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, &err), r->pool, err); const char *code = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk"; TST_ASSERT("oidc_proto_validate_code", oidc_proto_validate_code(r, NULL, jwt, "code id_token", code)); oidc_jwt_destroy(jwt); return 0; } static char * test_proto_authorization_request(request_rec *r) { oidc_provider_t provider; provider.issuer = "https://idp.example.com"; provider.authorization_endpoint_url = "https://idp.example.com/authorize"; provider.scope = "openid"; provider.client_id = "client_id"; provider.client_secret = NULL; provider.response_type = "code"; provider.auth_request_params = NULL; provider.request_object = NULL; provider.token_binding_policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL; const char *redirect_uri = "https://www.example.com/protected/"; const char *state = "12345"; oidc_proto_state_t *proto_state = oidc_proto_state_new(); oidc_proto_state_set_nonce(proto_state, "anonce"); oidc_proto_state_set_original_url(proto_state, "https://localhost/protected/index.php"); oidc_proto_state_set_original_method(proto_state, OIDC_METHOD_GET); oidc_proto_state_set_issuer(proto_state, provider.issuer); oidc_proto_state_set_response_type(proto_state, provider.response_type); oidc_proto_state_set_timestamp_now(proto_state); TST_ASSERT("oidc_proto_authorization_request (1)", oidc_proto_authorization_request(r, &provider, NULL, redirect_uri, state, proto_state, NULL, NULL, NULL, NULL) == HTTP_MOVED_TEMPORARILY); TST_ASSERT_STR("oidc_proto_authorization_request (2)", apr_table_get(r->headers_out, "Location"), "https://idp.example.com/authorize?response_type=code&scope=openid&client_id=client_id&state=12345&redirect_uri=https%3A%2F%2Fwww.example.com%2Fprotected%2F&nonce=anonce"); return 0; } static char * test_proto_validate_nonce(request_rec *r) { oidc_cfg *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); const char *nonce = "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,"; /* * { * "typ": "JWT", * "alg": "RS256", * "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM" * } * { * "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,", * "iat": 1411580876, * "at_hash": "yTqsoONZbuWbN6TbgevuDQ", * "sub": "6343a29c-5399-44a7-9b35-4990f4377c96", * "amr": "password", * "auth_time": 1411577267, * "idp": "idsrv", * "name": "ksonaty", * "iss": "https://agsync.com", * "aud": "agsync_implicit", * "exp": 1411584475, * "nbf": 1411580875 * } */ char *s_jwt = apr_pstrdup(r->pool, "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA"); oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err); TST_ASSERT("oidc_proto_validate_nonce (1)", oidc_proto_validate_nonce(r, c, &c->provider, nonce, jwt)); TST_ASSERT("oidc_proto_validate_nonce (2)", oidc_proto_validate_nonce( r, c, &c->provider, nonce, jwt) == FALSE); oidc_jwt_destroy(jwt); return 0; } static char * test_proto_validate_jwt(request_rec *r) { oidc_jwt_t *jwt = NULL; oidc_jose_error_t err; const char *s_secret = "secret"; const char *s_issuer = "https://localhost"; apr_time_t now = apr_time_sec(apr_time_now()); const char *s_jwt_header = "{" "\"alg\": \"HS256\"" "}"; const char *s_jwt_payload = "{" "\"nonce\": \"543210,\"," "\"iat\": %" APR_TIME_T_FMT "," "\"sub\": \"alice\"," "\"iss\": \"%s\"," "\"aud\": \"bob\"," "\"exp\": %" APR_TIME_T_FMT "}"; s_jwt_payload = apr_psprintf(r->pool, s_jwt_payload, now, s_issuer, now + 600); char *s_jwt_header_encoded = NULL; oidc_base64url_encode(r, &s_jwt_header_encoded, s_jwt_header, strlen(s_jwt_header), 1); char *s_jwt_payload_encoded = NULL; oidc_base64url_encode(r, &s_jwt_payload_encoded, s_jwt_payload, strlen(s_jwt_payload), 1); char *s_jwt_message = apr_psprintf(r->pool, "%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded); unsigned int md_len = 0; unsigned char md[EVP_MAX_MD_SIZE]; const EVP_MD *digest = EVP_get_digestbyname("sha256"); TST_ASSERT("HMAC", HMAC(digest, (const unsigned char * )s_secret, strlen(s_secret), (const unsigned char * )s_jwt_message, strlen(s_jwt_message), md, &md_len) != 0); char *s_jwt_signature_encoded = NULL; oidc_base64url_encode(r, &s_jwt_signature_encoded, (const char *) md, md_len, 1); char *s_jwt = apr_psprintf(r->pool, "%s.%s.%s", s_jwt_header_encoded, s_jwt_payload_encoded, s_jwt_signature_encoded); TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err); oidc_jwk_t *jwk = NULL; TST_ASSERT_ERR("oidc_util_create_symmetric_key", oidc_util_create_symmetric_key(r, s_secret, 0, NULL, TRUE, &jwk) == TRUE, r->pool, err); TST_ASSERT_ERR("oidc_util_create_symmetric_key (jwk)", jwk != NULL, r->pool, err); TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(r->pool, jwt, oidc_util_merge_symmetric_key(r->pool, NULL, jwk), &err), r->pool, err); TST_ASSERT_ERR("oidc_proto_validate_jwt", oidc_proto_validate_jwt(r, jwt, s_issuer, TRUE, TRUE, 10), r->pool, err); oidc_jwk_destroy(jwk); oidc_jwt_destroy(jwt); return 0; } static char * test_current_url(request_rec *r) { char *url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (1)", url, "https://www.example.com"); apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (2)", url, "https://www.outer.com"); apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com:654"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (3)", url, "https://www.outer.com:654"); apr_table_set(r->headers_in, "X-Forwarded-Port", "321"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (4)", url, "https://www.outer.com:321"); apr_table_set(r->headers_in, "X-Forwarded-Proto", "http"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (5)", url, "http://www.outer.com:321"); apr_table_set(r->headers_in, "X-Forwarded-Proto", "https , http"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (6)", url, "https://www.outer.com:321"); apr_table_unset(r->headers_in, "X-Forwarded-Host"); apr_table_unset(r->headers_in, "X-Forwarded-Port"); url = oidc_get_current_url(r); TST_ASSERT_STR("test_headers (7)", url, "https://www.example.com"); return 0; } static char * all_tests(apr_pool_t *pool, request_rec *r) { char *message; TST_RUN(test_jwt_parse, pool); TST_RUN(test_plaintext_jwt_parse, pool); TST_RUN(test_jwt_get_string, pool); TST_RUN(test_jwk_parse_json, pool); TST_RUN(test_plaintext_decrypt, pool); TST_RUN(test_plaintext_decrypt2, pool); TST_RUN(test_plaintext_decrypt_symmetric, pool); TST_RUN(test_jwt_decrypt, pool); #if (OPENSSL_VERSION_NUMBER >= 0x1000100f) TST_RUN(test_jwt_decrypt_gcm, pool); #endif #if (APR_JWS_EC_SUPPORT) TST_RUN(test_jwt_verify_ec, pool); #endif TST_RUN(test_jwt_verify_rsa, pool); TST_RUN(test_jwt_sign_verify, pool); TST_RUN(test_proto_validate_access_token, r); TST_RUN(test_proto_validate_code, r); TST_RUN(test_proto_authorization_request, r); TST_RUN(test_proto_validate_nonce, r); TST_RUN(test_proto_validate_jwt, r); TST_RUN(test_current_url, r); return 0; } typedef struct oidc_dir_cfg oidc_dir_cfg; static request_rec * test_setup(apr_pool_t *pool) { const unsigned int kIdx = 0; const unsigned int kEls = kIdx + 1; request_rec *request = (request_rec *) apr_pcalloc(pool, sizeof(request_rec)); request->pool = pool; request->headers_in = apr_table_make(request->pool, 0); request->headers_out = apr_table_make(request->pool, 0); request->err_headers_out = apr_table_make(request->pool, 0); apr_table_set(request->headers_in, "Host", "www.example.com"); apr_table_set(request->headers_in, "OIDC_foo", "some-value"); apr_table_set(request->headers_in, "Cookie", "foo=bar; " "mod_auth_openidc_session" "=0123456789abcdef; baz=zot"); request->server = apr_pcalloc(request->pool, sizeof(struct server_rec)); request->server->process = apr_pcalloc(request->pool, sizeof(struct process_rec)); request->server->process->pool = request->pool; request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec)); request->connection->local_addr = apr_pcalloc(request->pool, sizeof(apr_sockaddr_t)); apr_pool_userdata_set("https", "scheme", NULL, request->pool); request->server->server_hostname = "www.example.com"; request->connection->local_addr->port = 443; request->unparsed_uri = "/bla?foo=bar¶m1=value1"; request->args = "foo=bar¶m1=value1"; apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar¶m1=value1", &request->parsed_uri); auth_openidc_module.module_index = kIdx; oidc_cfg *cfg = oidc_create_server_config(request->pool, request->server); cfg->provider.issuer = "https://idp.example.com"; cfg->provider.authorization_endpoint_url = "https://idp.example.com/authorize"; cfg->provider.scope = "openid"; cfg->provider.client_id = "client_id"; cfg->provider.token_binding_policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL; cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); ap_set_module_config(request->server->module_config, &auth_openidc_module, cfg); ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg); cfg->crypto_passphrase = "12345678901234567890123456789012"; cfg->cache = &oidc_cache_shm; cfg->cache_cfg = NULL; cfg->cache_shm_size_max = 500; cfg->cache_shm_entry_size_max = 16384 + 255 + 17; cfg->cache_encrypt = 1; if (cfg->cache->post_config(request->server) != OK) { printf("cfg->cache->post_config failed!\n"); exit(-1); } return request; } int main(int argc, char **argv, char **env) { if (apr_app_initialize(&argc, (const char * const **) argv, (const char * const **) env) != APR_SUCCESS) { printf("apr_app_initialize failed\n"); return -1; } apr_pool_t *pool = NULL; apr_pool_create(&pool, NULL); request_rec *r = test_setup(pool); OpenSSL_add_all_algorithms(); char *result = all_tests(pool, r); if (result != 0) { printf("Failed: %s\n", result); } else { printf("All %d tests passed!\n", test_nr_run); } EVP_cleanup(); apr_pool_destroy(pool); apr_terminate(); return result != 0; } mod_auth_openidc-2.3.3/test/test-cmd.c0000644000076500000240000003477313202535004017531 0ustar hzandbeltstaff/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /*************************************************************************** * Copyright (C) 2013-2017 Ping Identity Corporation * All rights reserved. * * For further information please contact: * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu * **************************************************************************/ #include #include #include #include #include #include #include #include #include int usage(int argc, char **argv, const char *msg) { fprintf(stderr, "Usage: %s %s\n", argv[0], msg ? msg : "[ sign | verify | jwk2cert | cert2jwk | enckey | hash_base64url | timestamp] "); return -1; } int file_read(apr_pool_t *pool, const char *path, char **rbuf) { apr_file_t *fd = NULL; char s_err[128]; int rc; apr_size_t bytes_read = 0; apr_finfo_t finfo; apr_size_t len; rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, pool); if (rc != APR_SUCCESS) { fprintf(stderr, "could not open file %s: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); return -1; } apr_file_info_get(&finfo, APR_FINFO_NORM, fd); len = (apr_size_t) finfo.size; *rbuf = apr_pcalloc(pool, len + 1); rc = apr_file_read_full(fd, *rbuf, len, &bytes_read); if (rc != APR_SUCCESS) { fprintf(stderr, "could not read file %s: %s", path, apr_strerror(rc, s_err, sizeof(s_err))); return -1; } (*rbuf)[bytes_read] = '\0'; bytes_read--; while ((*rbuf)[bytes_read] == '\n') { (*rbuf)[bytes_read] = '\0'; bytes_read --; } apr_file_close(fd); return 0; } int sign(int argc, char **argv, apr_pool_t *pool) { if (argc <= 4) return usage(argc, argv, "sign "); char *s_jwt = NULL, *s_jwk = NULL; const char *cser = NULL; if (file_read(pool, argv[3], &s_jwt) != 0) return -1; if (file_read(pool, argv[4], &s_jwk) != 0) return -1; cjose_err cjose_err; cjose_header_t *hdr = cjose_header_new(&cjose_err); cjose_header_set(hdr, "alg", argv[2], &cjose_err); cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } cjose_jws_t *jws = cjose_jws_sign(jwk, hdr, (const uint8_t *) s_jwt, strlen(s_jwt), &cjose_err); if (jws == NULL) { fprintf(stderr, "could not sign JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } if (cjose_jws_export(jws, &cser, &cjose_err) == FALSE) { fprintf(stderr, "could not serialize JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } fprintf(stdout, "%s", cser); cjose_jws_release(jws); cjose_jwk_release(jwk); return 0; } int verify(int argc, char **argv, apr_pool_t *pool) { if (argc <= 3) return usage(argc, argv, "verify "); char *s_jwt = NULL, *s_jwk = NULL; if (file_read(pool, argv[2], &s_jwt) != 0) return -1; if (file_read(pool, argv[3], &s_jwk) != 0) return -1; cjose_err cjose_err; cjose_jws_t *jws = cjose_jws_import(s_jwt, strlen(s_jwt), &cjose_err); if (jws == NULL) { fprintf(stderr, "could not import JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } if (cjose_jws_verify(jws, jwk, &cjose_err) == FALSE) { fprintf(stderr, "could not verify JWS: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } uint8_t *plaintext = NULL; size_t plaintext_len = 0; if (cjose_jws_get_plaintext(jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) { fprintf(stderr, "could not get plaintext: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } fprintf(stdout, "%s", plaintext); cjose_jws_release(jws); cjose_jwk_release(jwk); return 0; } int mkcert(RSA *rsa, X509 **x509p, EVP_PKEY **pkeyp, int serial, int days) { X509 *x; EVP_PKEY *pk; X509_NAME *name = NULL; if ((pkeyp == NULL) || (*pkeyp == NULL)) { if ((pk = EVP_PKEY_new()) == NULL) return -1; } else pk = *pkeyp; if ((x509p == NULL) || (*x509p == NULL)) { if ((x = X509_new()) == NULL) return -1; } else x = *x509p; if (!EVP_PKEY_assign_RSA(pk, rsa)) return -1; X509_set_version(x, 2); ASN1_INTEGER_set(X509_get_serialNumber(x), serial); X509_gmtime_adj(X509_get_notBefore(x), 0); X509_gmtime_adj(X509_get_notAfter(x), (long) 60 * 60 * 24 * days); X509_set_pubkey(x, pk); name = X509_get_subject_name(x); X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *) "NL", -1, -1, 0); X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *) "Ping Identity", -1, -1, 0); X509_set_issuer_name(x, name); if (!X509_sign(x, pk, EVP_md5())) return -1; *x509p = x; *pkeyp = pk; return 0; } int jwk2cert(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "jwk2cert "); char *s_jwk = NULL; if (file_read(pool, argv[2], &s_jwk) != 0) return -1; cjose_err cjose_err; cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err); if (jwk == NULL) { fprintf(stderr, "could not import JWK: %s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line); return -1; } if (cjose_jwk_get_kty(jwk, &cjose_err) != CJOSE_JWK_KTY_RSA) { fprintf(stderr, "wrong key type"); return -1; } RSA *rsa = cjose_jwk_get_keydata(jwk, &cjose_err); //PEM_write_RSAPublicKey(stdout, rsa); PEM_write_RSA_PUBKEY(stdout, rsa); X509 *x509 = NULL; EVP_PKEY *pkey = NULL; if (mkcert(rsa, &x509, &pkey, 0, 365) != 0) return -1; //RSA_print_fp(stdout,pkey->pkey.rsa,0); //X509_print_fp(stdout,x509); //PEM_write_PrivateKey(stdout,pkey,NULL,NULL,0,NULL, NULL); PEM_write_X509(stdout, x509); X509_free(x509); EVP_PKEY_free(pkey); return 0; } int cert2jwk(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "cert2jwk "); oidc_jwk_t *jwk = NULL; oidc_jose_error_t err; if (oidc_jwk_parse_rsa_public_key(pool, NULL, argv[2], &jwk, &err) == FALSE) { fprintf(stderr, "oidc_jwk_parse_rsa_public_key failed: %s", oidc_jose_e2s(pool, err)); return -1; } char *s_json = NULL; if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) { fprintf(stderr, "oidc_jwk_to_json failed: %s", oidc_jose_e2s(pool, err)); return -1; } fprintf(stdout, "%s", s_json); oidc_jwk_destroy(jwk); return 0; } extern module AP_MODULE_DECLARE_DATA auth_openidc_module; typedef struct oidc_dir_cfg oidc_dir_cfg; static request_rec * request_setup(apr_pool_t *pool) { const unsigned int kIdx = 0; const unsigned int kEls = kIdx + 1; request_rec *request = (request_rec *) apr_pcalloc(pool, sizeof(request_rec)); request->pool = pool; request->headers_in = apr_table_make(request->pool, 0); request->headers_out = apr_table_make(request->pool, 0); request->err_headers_out = apr_table_make(request->pool, 0); apr_table_set(request->headers_in, "Host", "www.example.com"); apr_table_set(request->headers_in, "OIDC_foo", "some-value"); apr_table_set(request->headers_in, "Cookie", "foo=bar; " "mod_auth_openidc_session" "=0123456789abcdef; baz=zot"); request->server = apr_pcalloc(request->pool, sizeof(struct server_rec)); request->server->process = apr_pcalloc(request->pool, sizeof(struct process_rec)); request->server->process->pool = request->pool; request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec)); request->connection->local_addr = apr_pcalloc(request->pool, sizeof(apr_sockaddr_t)); apr_pool_userdata_set("https", "scheme", NULL, request->pool); request->server->server_hostname = "www.example.com"; request->connection->local_addr->port = 443; request->unparsed_uri = "/bla?foo=bar¶m1=value1"; request->args = "foo=bar¶m1=value1"; apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar¶m1=value1", &request->parsed_uri); auth_openidc_module.module_index = kIdx; oidc_cfg *cfg = oidc_create_server_config(request->pool, request->server); cfg->provider.issuer = "https://idp.example.com"; cfg->provider.authorization_endpoint_url = "https://idp.example.com/authorize"; cfg->provider.scope = "openid"; cfg->provider.client_id = "client_id"; cfg->redirect_uri = "https://www.example.com/protected/"; oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL); request->server->module_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); request->per_dir_config = apr_pcalloc(request->pool, sizeof(ap_conf_vector_t *) * kEls); ap_set_module_config(request->server->module_config, &auth_openidc_module, cfg); ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg); cfg->cache = &oidc_cache_shm; cfg->cache_cfg = NULL; cfg->cache_shm_size_max = 500; cfg->cache_shm_entry_size_max = 16384 + 255 + 17; if (cfg->cache->post_config(request->server) != OK) { printf("cfg->cache->post_config failed!\n"); exit(-1); } return request; } int enckey(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "enckey [hash] [key-length]"); request_rec *r = request_setup(pool); oidc_jwk_t *jwk = NULL; if (oidc_util_create_symmetric_key(r, argv[2], argc > 4 ? atoi(argv[4]) : 0, argc > 3 ? argv[3] : NULL, FALSE, &jwk) == FALSE) { fprintf(stderr, "oidc_util_create_symmetric_key failed"); return -1; } oidc_jose_error_t err; char *s_json = NULL; if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) { fprintf(stderr, "oidc_jwk_to_json failed"); return -1; } cjose_err cjose_err; int src_len = cjose_jwk_get_keysize(jwk->cjose_jwk, &cjose_err) / 8; int enc_len = apr_base64_encode_len(src_len); char *b64 = apr_palloc(r->pool, enc_len); apr_base64_encode(b64, (const char *) cjose_jwk_get_keydata(jwk->cjose_jwk, &cjose_err), src_len); fprintf(stdout, "\nJWK:\n%s\n\nbase64:\n%s\n\n", s_json, b64); return 0; } int hash_base64url(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "hash_base64url [algo]"); char *algo = argc > 3 ? argv[3] : "sha256"; char *output = NULL; request_rec *r = request_setup(pool); if (oidc_util_hash_string_and_base64url_encode(r, algo, argv[2], &output) == FALSE) { fprintf(stderr, "oidc_util_hash_string_and_base64url_encode failed"); return -1; } fprintf(stdout, "%s\n", output); return 0; } int timestamp(int argc, char **argv, apr_pool_t *pool) { if (argc <= 2) return usage(argc, argv, "timestamp "); long delta = strtol(argv[2], NULL, 10); apr_time_t t1 = apr_time_now() + apr_time_from_sec(delta); char *s = apr_psprintf(pool, "%" APR_TIME_T_FMT, t1); fprintf(stderr, "timestamp (1) = %s\n", s); apr_time_t t2; sscanf(s, "%" APR_TIME_T_FMT, &t2); fprintf(stderr, "timestamp (2) = %" APR_TIME_T_FMT "\n", t2); char buf[APR_RFC822_DATE_LEN + 1]; apr_rfc822_date(buf, t2); fprintf(stderr, "timestamp (3): %s (%" APR_TIME_T_FMT " secs from now)\n", buf, apr_time_sec(t2 - apr_time_now())); return 0; } int main(int argc, char **argv, char **env) { if (argc <= 1) return usage(argc, argv, NULL); if (apr_app_initialize(&argc, (const char * const **) argv, (const char * const **) env) != APR_SUCCESS) { printf("apr_app_initialize failed\n"); return -1; } OpenSSL_add_all_algorithms(); apr_pool_t *pool = NULL; apr_pool_create(&pool, NULL); if (strcmp(argv[1], "sign") == 0) return sign(argc, argv, pool); if (strcmp(argv[1], "verify") == 0) return verify(argc, argv, pool); if (strcmp(argv[1], "jwk2cert") == 0) return jwk2cert(argc, argv, pool); if (strcmp(argv[1], "cert2jwk") == 0) return cert2jwk(argc, argv, pool); if (strcmp(argv[1], "enckey") == 0) return enckey(argc, argv, pool); if (strcmp(argv[1], "hash_base64url") == 0) return hash_base64url(argc, argv, pool); if (strcmp(argv[1], "timestamp") == 0) return timestamp(argc, argv, pool); apr_pool_destroy(pool); apr_terminate(); return usage(argc, argv, NULL); } mod_auth_openidc-2.3.3/test/stub.c0000644000076500000240000001122713200625410016752 0ustar hzandbeltstaff#include #include #include #include #include #define ap_HOOK_check_user_id_t void AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf, const char * const *aszPre, const char * const *aszSucc, int nOrder, int type) { } AP_DECLARE(apr_status_t) ap_register_auth_provider(apr_pool_t *pool, const char *provider_group, const char *provider_name, const char *provider_version, const void *provider, int type) { return 0; } AP_DECLARE(apr_status_t) ap_unixd_set_global_mutex_perms(apr_global_mutex_t *gmutex) { return 0; } AP_DECLARE(const char *) ap_auth_type(request_rec *r) { return "openid-connect"; } AP_DECLARE(const char *) ap_auth_name(request_rec *r) { return NULL; } AP_DECLARE(long) ap_get_client_block(request_rec * r, char * buffer, apr_size_t bufsiz) { return 0; } AP_DECLARE(char *) ap_getword(apr_pool_t *p, const char **line, char stop) { return ""; } AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) { return ""; } AP_DECLARE(char *) ap_getword_white(apr_pool_t *p, const char **line) { return 0; } AP_DECLARE(int) ap_hook_check_user_id(request_rec *r) { return 0; } AP_DECLARE(int) ap_hook_auth_checker(request_rec *r) { return 0; } AP_DECLARE(int) ap_hook_fixups(request_rec *r) { return 0; } AP_DECLARE(void) ap_hook_post_config( int (*post_config)(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2, server_rec *s), const char * const *aszPre, const char * const *aszSucc, int nOrder) { } AP_DECLARE(void) ap_hook_child_init( void (*child_init)(apr_pool_t *p, server_rec *s), const char * const *aszPre, const char * const *aszSucc, int nOrder) { } AP_DECLARE(void) ap_hook_handler( int (*handler)(request_rec *r), const char * const *aszPre, const char * const *aszSucc, int nOrder) { } AP_DECLARE(int) ap_is_initial_req(request_rec *r) { return 0; } #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, int level, apr_status_t status, const server_rec *s, const char *fmt, ...) { #else AP_DECLARE(void) ap_log_error(const char *file, int line, int level, apr_status_t status, const server_rec *s, const char *fmt, ...) { #endif if (level < APLOG_DEBUG) { fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } } #if MODULE_MAGIC_NUMBER_MAJOR >= 20100714 AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, int level, apr_status_t status, const request_rec *r, const char *fmt, ...) { #else AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level, apr_status_t status, const request_rec *r, const char *fmt, ...) { #endif if (level < APLOG_DEBUG) { fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } } AP_DECLARE(void) ap_note_auth_failure(request_rec *r) { } AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *filter, apr_bucket_brigade *bucket) { return APR_SUCCESS; } AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r) { return NULL; } const char *ap_run_http_scheme(const request_rec *r) { char *rv; apr_pool_userdata_get((void **) &rv, "scheme", r->pool); return (const char *) rv; } AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) { } AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd, void *struct_ptr, int arg) { return ""; } AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { return ""; } AP_DECLARE_NONSTD(const char *) ap_set_int_slot(cmd_parms *cmd, void *struct_ptr, const char *arg) { return ""; } AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) { return 0; } AP_DECLARE(int) ap_should_client_block(request_rec *r) { return 0; } AP_DECLARE(int) ap_unescape_url(char *url) { return 0; } AP_DECLARE(apr_status_t) unixd_set_global_mutex_perms( apr_global_mutex_t *gmutex) { return APR_SUCCESS; } AP_DECLARE(const char *) ap_get_server_name(request_rec *r) { return "www.example.com"; } AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) { return ""; } mod_auth_openidc-2.3.3/configure0000755000076500000240000047552213203321123016572 0ustar hzandbeltstaff#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for mod_auth_openidc 2.3.3. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 as_fn_exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. $as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: hans.zandbelt@zmartzone.eu about your system, including $0: any error possibly output before this message. Then $0: install a modern shell, or manually run the script $0: under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='mod_auth_openidc' PACKAGE_TARNAME='mod_auth_openidc' PACKAGE_VERSION='2.3.3' PACKAGE_STRING='mod_auth_openidc 2.3.3' PACKAGE_BUGREPORT='hans.zandbelt@zmartzone.eu' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='LTLIBOBJS LIBOBJS JQ_LIBS JQ_CFLAGS HAVE_LIBJQ EGREP GREP CPP OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC HAVE_LIBHIREDIS HIREDIS_LIBS HIREDIS_CFLAGS PCRE_LIBS PCRE_CFLAGS CJOSE_LIBS CJOSE_CFLAGS JANSSON_LIBS JANSSON_CFLAGS APR_LIBS APR_CFLAGS OPENSSL_LIBS OPENSSL_CFLAGS CURL_LIBS CURL_CFLAGS PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG APXS2_OPTS APXS2 NAMEVER target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_apxs2 with_hiredis with_jq ' ac_precious_vars='build_alias host_alias target_alias APXS2_OPTS PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR CURL_CFLAGS CURL_LIBS OPENSSL_CFLAGS OPENSSL_LIBS APR_CFLAGS APR_LIBS JANSSON_CFLAGS JANSSON_LIBS CJOSE_CFLAGS CJOSE_LIBS PCRE_CFLAGS PCRE_LIBS HIREDIS_CFLAGS HIREDIS_LIBS CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures mod_auth_openidc 2.3.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/mod_auth_openidc] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of mod_auth_openidc 2.3.3:";; esac cat <<\_ACEOF Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-apxs2=PATH Full path to the apxs2 executable. --with-hiredis support Redis [default=check] --with-jq=PATH location of your libjq installation Some influential environment variables: APXS2_OPTS Additional command line options to pass to apxs2. PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path CURL_CFLAGS C compiler flags for CURL, overriding pkg-config CURL_LIBS linker flags for CURL, overriding pkg-config OPENSSL_CFLAGS C compiler flags for OPENSSL, overriding pkg-config OPENSSL_LIBS linker flags for OPENSSL, overriding pkg-config APR_CFLAGS C compiler flags for APR, overriding pkg-config APR_LIBS linker flags for APR, overriding pkg-config JANSSON_CFLAGS C compiler flags for JANSSON, overriding pkg-config JANSSON_LIBS linker flags for JANSSON, overriding pkg-config CJOSE_CFLAGS C compiler flags for CJOSE, overriding pkg-config CJOSE_LIBS linker flags for CJOSE, overriding pkg-config PCRE_CFLAGS C compiler flags for PCRE, overriding pkg-config PCRE_LIBS linker flags for PCRE, overriding pkg-config HIREDIS_CFLAGS C compiler flags for HIREDIS, overriding pkg-config HIREDIS_LIBS linker flags for HIREDIS, overriding pkg-config CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF mod_auth_openidc configure 2.3.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists, giving a warning if it cannot be compiled using # the include files in INCLUDES and setting the cache variable VAR # accordingly. ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if eval \${$3+:} false; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } else # Is the header compilable? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 $as_echo_n "checking $2 usability... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_header_compiler=yes else ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 $as_echo "$ac_header_compiler" >&6; } # Is the header present? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 $as_echo_n "checking $2 presence... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include <$2> _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : ac_header_preproc=yes else ac_header_preproc=no fi rm -f conftest.err conftest.i conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 $as_echo "$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( yes:no: ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 $as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ;; no:yes:* ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 $as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 $as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 $as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ( $as_echo "## ----------------------------------------- ## ## Report this to hans.zandbelt@zmartzone.eu ## ## ----------------------------------------- ##" ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_mongrel # ac_fn_c_try_run LINENO # ---------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. Assumes # that executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then : ac_retval=0 else $as_echo "$as_me: program exited with status $ac_status" >&5 $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval \${$3+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by mod_auth_openidc $as_me 2.3.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu NAMEVER=mod_auth_openidc-2.3.3 # This section defines the --with-apxs2 option. # Check whether --with-apxs2 was given. if test "${with_apxs2+set}" = set; then : withval=$with_apxs2; APXS2=${withval} fi if test "x$APXS2" = "x"; then # The user didn't specify the --with-apxs2-option. # Search for apxs2 in the specified directories # Extract the first word of "apxs2", so it can be a program name with args. set dummy apxs2; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_APXS2+:} false; then : $as_echo_n "(cached) " >&6 else case $APXS2 in [\\/]* | ?:[\\/]*) ac_cv_path_APXS2="$APXS2" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_APXS2="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi APXS2=$ac_cv_path_APXS2 if test -n "$APXS2"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $APXS2" >&5 $as_echo "$APXS2" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$APXS2" = "x"; then # Didn't find apxs2 in any of the specified directories. # Search for apxs instead. # Extract the first word of "apxs", so it can be a program name with args. set dummy apxs; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_APXS2+:} false; then : $as_echo_n "(cached) " >&6 else case $APXS2 in [\\/]* | ?:[\\/]*) ac_cv_path_APXS2="$APXS2" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" for as_dir in $as_dummy do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_APXS2="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi APXS2=$ac_cv_path_APXS2 if test -n "$APXS2"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $APXS2" >&5 $as_echo "$APXS2" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi # Test if $APXS2 exists and is an executable. if test ! -x "$APXS2"; then # $APXS2 isn't a executable file. as_fn_error $? " Could not find apxs2. Please specify the path to apxs2 using the --with-apxs2=/full/path/to/apxs2 option. The executable may also be named 'apxs'. " "$LINENO" 5 fi # Replace any occurrences of @APXS2@ with the value of $APXS2 in the Makefile. # Use environment varilable APXS2_OPTS to pass params to APXS2 command # We need the curl library for HTTP callouts. if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 $as_echo "$PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : $as_echo_n "(cached) " >&6 else case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 $as_echo "$ac_pt_PKG_CONFIG" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 $as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } PKG_CONFIG="" fi fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcurl" >&5 $as_echo_n "checking for libcurl... " >&6; } if test -n "$CURL_CFLAGS"; then pkg_cv_CURL_CFLAGS="$CURL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcurl") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CURL_CFLAGS=`$PKG_CONFIG --cflags "libcurl" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$CURL_LIBS"; then pkg_cv_CURL_LIBS="$CURL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl\""; } >&5 ($PKG_CONFIG --exists --print-errors "libcurl") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CURL_LIBS=`$PKG_CONFIG --libs "libcurl" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then CURL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcurl" 2>&1` else CURL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcurl" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$CURL_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libcurl) were not met: $CURL_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables CURL_CFLAGS and CURL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables CURL_CFLAGS and CURL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else CURL_CFLAGS=$pkg_cv_CURL_CFLAGS CURL_LIBS=$pkg_cv_CURL_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi # We need OpenSSL for crypto and HTTPS callouts. pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl" >&5 $as_echo_n "checking for openssl... " >&6; } if test -n "$OPENSSL_CFLAGS"; then pkg_cv_OPENSSL_CFLAGS="$OPENSSL_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"openssl\""; } >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_OPENSSL_CFLAGS=`$PKG_CONFIG --cflags "openssl" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$OPENSSL_LIBS"; then pkg_cv_OPENSSL_LIBS="$OPENSSL_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"openssl\""; } >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_OPENSSL_LIBS=`$PKG_CONFIG --libs "openssl" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl" 2>&1` else OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$OPENSSL_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (openssl) were not met: $OPENSSL_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables OPENSSL_CFLAGS and OPENSSL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables OPENSSL_CFLAGS and OPENSSL_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr-1, apr-util-1" >&5 $as_echo_n "checking for apr-1, apr-util-1... " >&6; } if test -n "$APR_CFLAGS"; then pkg_cv_APR_CFLAGS="$APR_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"apr-1, apr-util-1\""; } >&5 ($PKG_CONFIG --exists --print-errors "apr-1, apr-util-1") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_APR_CFLAGS=`$PKG_CONFIG --cflags "apr-1, apr-util-1" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$APR_LIBS"; then pkg_cv_APR_LIBS="$APR_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"apr-1, apr-util-1\""; } >&5 ($PKG_CONFIG --exists --print-errors "apr-1, apr-util-1") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_APR_LIBS=`$PKG_CONFIG --libs "apr-1, apr-util-1" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then APR_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "apr-1, apr-util-1" 2>&1` else APR_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "apr-1, apr-util-1" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$APR_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (apr-1, apr-util-1) were not met: $APR_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables APR_CFLAGS and APR_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables APR_CFLAGS and APR_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else APR_CFLAGS=$pkg_cv_APR_CFLAGS APR_LIBS=$pkg_cv_APR_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi # We need Jansson for JSON parsing. pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for jansson" >&5 $as_echo_n "checking for jansson... " >&6; } if test -n "$JANSSON_CFLAGS"; then pkg_cv_JANSSON_CFLAGS="$JANSSON_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson\""; } >&5 ($PKG_CONFIG --exists --print-errors "jansson") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JANSSON_CFLAGS=`$PKG_CONFIG --cflags "jansson" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$JANSSON_LIBS"; then pkg_cv_JANSSON_LIBS="$JANSSON_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson\""; } >&5 ($PKG_CONFIG --exists --print-errors "jansson") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JANSSON_LIBS=`$PKG_CONFIG --libs "jansson" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then JANSSON_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jansson" 2>&1` else JANSSON_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jansson" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$JANSSON_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (jansson) were not met: $JANSSON_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables JANSSON_CFLAGS and JANSSON_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables JANSSON_CFLAGS and JANSSON_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else JANSSON_CFLAGS=$pkg_cv_JANSSON_CFLAGS JANSSON_LIBS=$pkg_cv_JANSSON_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi # cjose pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cjose" >&5 $as_echo_n "checking for cjose... " >&6; } if test -n "$CJOSE_CFLAGS"; then pkg_cv_CJOSE_CFLAGS="$CJOSE_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"cjose\""; } >&5 ($PKG_CONFIG --exists --print-errors "cjose") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CJOSE_CFLAGS=`$PKG_CONFIG --cflags "cjose" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$CJOSE_LIBS"; then pkg_cv_CJOSE_LIBS="$CJOSE_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"cjose\""; } >&5 ($PKG_CONFIG --exists --print-errors "cjose") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_CJOSE_LIBS=`$PKG_CONFIG --libs "cjose" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then CJOSE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "cjose" 2>&1` else CJOSE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "cjose" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$CJOSE_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (cjose) were not met: $CJOSE_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables CJOSE_CFLAGS and CJOSE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables CJOSE_CFLAGS and CJOSE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else CJOSE_CFLAGS=$pkg_cv_CJOSE_CFLAGS CJOSE_LIBS=$pkg_cv_CJOSE_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi # PCRE pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libpcre" >&5 $as_echo_n "checking for libpcre... " >&6; } if test -n "$PCRE_CFLAGS"; then pkg_cv_PCRE_CFLAGS="$PCRE_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre\""; } >&5 ($PKG_CONFIG --exists --print-errors "libpcre") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PCRE_CFLAGS=`$PKG_CONFIG --cflags "libpcre" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$PCRE_LIBS"; then pkg_cv_PCRE_LIBS="$PCRE_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre\""; } >&5 ($PKG_CONFIG --exists --print-errors "libpcre") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_PCRE_LIBS=`$PKG_CONFIG --libs "libpcre" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then PCRE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre" 2>&1` else PCRE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$PCRE_PKG_ERRORS" >&5 as_fn_error $? "Package requirements (libpcre) were not met: $PCRE_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables PCRE_CFLAGS and PCRE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables PCRE_CFLAGS and PCRE_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details" "$LINENO" 5; } else PCRE_CFLAGS=$pkg_cv_PCRE_CFLAGS PCRE_LIBS=$pkg_cv_PCRE_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi # Redis # Check whether --with-hiredis was given. if test "${with_hiredis+set}" = set; then : withval=$with_hiredis; else with_hiredis=yes fi case "$with_hiredis" in #( yes) : if test "$HIREDIS_LIBS" == ""; then pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for hiredis" >&5 $as_echo_n "checking for hiredis... " >&6; } if test -n "$HIREDIS_CFLAGS"; then pkg_cv_HIREDIS_CFLAGS="$HIREDIS_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5 ($PKG_CONFIG --exists --print-errors "hiredis") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_HIREDIS_CFLAGS=`$PKG_CONFIG --cflags "hiredis" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$HIREDIS_LIBS"; then pkg_cv_HIREDIS_LIBS="$HIREDIS_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5 ($PKG_CONFIG --exists --print-errors "hiredis") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_HIREDIS_LIBS=`$PKG_CONFIG --libs "hiredis" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then HIREDIS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "hiredis" 2>&1` else HIREDIS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "hiredis" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$HIREDIS_PKG_ERRORS" >&5 HAVE_LIBHIREDIS=0 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } HAVE_LIBHIREDIS=0 else HIREDIS_CFLAGS=$pkg_cv_HIREDIS_CFLAGS HIREDIS_LIBS=$pkg_cv_HIREDIS_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } HAVE_LIBHIREDIS=1 fi ; else HAVE_LIBHIREDIS=1 ; fi ;; #( no) : HAVE_LIBHIREDIS=0 ;; #( *) : pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for hiredis" >&5 $as_echo_n "checking for hiredis... " >&6; } if test -n "$HIREDIS_CFLAGS"; then pkg_cv_HIREDIS_CFLAGS="$HIREDIS_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5 ($PKG_CONFIG --exists --print-errors "hiredis") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_HIREDIS_CFLAGS=`$PKG_CONFIG --cflags "hiredis" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$HIREDIS_LIBS"; then pkg_cv_HIREDIS_LIBS="$HIREDIS_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5 ($PKG_CONFIG --exists --print-errors "hiredis") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_HIREDIS_LIBS=`$PKG_CONFIG --libs "hiredis" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then HIREDIS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "hiredis" 2>&1` else HIREDIS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "hiredis" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$HIREDIS_PKG_ERRORS" >&5 HAVE_LIBHIREDIS=0 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } HAVE_LIBHIREDIS=0 else HIREDIS_CFLAGS=$pkg_cv_HIREDIS_CFLAGS HIREDIS_LIBS=$pkg_cv_HIREDIS_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } HAVE_LIBHIREDIS=1 fi ;; esac # JQ HAVE_LIBJQ=0 # Check whether --with-jq was given. if test "${with_jq+set}" = set; then : withval=$with_jq; fi if test -n "$with_jq" then JQ_CFLAGS="-I$with_jq/include" JQ_LIBS="-L$with_jq/lib -ljq" CPPFLAGS="$JQ_CFLAGS $CPPFLAGS" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if ${ac_cv_prog_ac_ct_CC+:} false; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 $as_echo_n "checking whether the C compiler works... " >&6; } ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi if test -z "$ac_file"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 $as_echo_n "checking for C compiler default output file name... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 $as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 $as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 $as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 $as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5; } fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 $as_echo "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } if ${ac_cv_objext+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 $as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } if ${ac_cv_c_compiler_gnu+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 $as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } if ${ac_cv_prog_cc_g+:} false; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes else CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 $as_echo "$ac_cv_prog_cc_g" >&6; } if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } if ${ac_cv_prog_cc_c89+:} false; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not '\xHH' hex character constants. These don't provoke an error unfortunately, instead are silently treated as 'x'. The following induces an error, until -std is added to get proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an array size at least. It's necessary to write '\x00'==0 to get something that's true only with -std. */ int osf4_cc_array ['\x00' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) 'x' int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c89" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c89" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac if test "x$ac_cv_prog_cc_c89" != xno; then : fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 $as_echo_n "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if ${ac_cv_prog_CPP+:} false; then : $as_echo_n "(cached) " >&6 else # Double quotes because CPP needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 $as_echo "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 $as_echo_n "checking for grep that handles long lines and -e... " >&6; } if ${ac_cv_path_GREP+:} false; then : $as_echo_n "(cached) " >&6 else if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 $as_echo "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 $as_echo_n "checking for egrep... " >&6; } if ${ac_cv_path_EGREP+:} false; then : $as_echo_n "(cached) " >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 $as_echo "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if ${ac_cv_header_stdc+:} false; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in jq.h do : ac_fn_c_check_header_mongrel "$LINENO" "jq.h" "ac_cv_header_jq_h" "$ac_includes_default" if test "x$ac_cv_header_jq_h" = xyes; then : cat >>confdefs.h <<_ACEOF #define HAVE_JQ_H 1 _ACEOF else HAVE_LIBJQ=0 fi done LDFLAGS="$JQ_LIBS $LDFLAGS" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for jq_init in -ljq" >&5 $as_echo_n "checking for jq_init in -ljq... " >&6; } if ${ac_cv_lib_jq_jq_init+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-ljq $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char jq_init (); int main () { return jq_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_jq_jq_init=yes else ac_cv_lib_jq_jq_init=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jq_jq_init" >&5 $as_echo "$ac_cv_lib_jq_jq_init" >&6; } if test "x$ac_cv_lib_jq_jq_init" = xyes; then : HAVE_LIBJQ=1 else HAVE_LIBJQ=0 fi if test "x$have_jq" = "x0" ; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \"cannot find library for -ljq.\"" >&5 $as_echo "$as_me: WARNING: \"cannot find library for -ljq.\"" >&2;} fi fi # Create Makefile from Makefile.in ac_config_files="$ac_config_files Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' # Transform confdefs.h into DEFS. # Protect against shell expansion while executing Makefile rules. # Protect against Makefile macro expansion. # # If the first sed substitution is executed (which looks for macros that # take arguments), then branch to the quote section. Otherwise, # look for a macro that doesn't take arguments. ac_script=' :mline /\\$/{ N s,\\\n,, b mline } t clear :clear s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g t quote s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\[/\\&/g s/\]/\\&/g s/\$/$$/g H :any ${ g s/^\n// s/\n/ /g p } ' DEFS=`sed -n "$ac_script" confdefs.h` ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by mod_auth_openidc $as_me 2.3.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE Configuration files: $config_files Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ mod_auth_openidc config.status 2.3.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" eval set X " :F $CONFIG_FILES " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi mod_auth_openidc-2.3.3/configure.ac0000644000076500000240000000572113203320656017151 0ustar hzandbeltstaffAC_INIT([mod_auth_openidc],[2.3.3],[hans.zandbelt@zmartzone.eu]) AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION()) # This section defines the --with-apxs2 option. AC_ARG_WITH( [apxs2], [ --with-apxs2=PATH Full path to the apxs2 executable.], [ APXS2=${withval} ],) if test "x$APXS2" = "x"; then # The user didn't specify the --with-apxs2-option. # Search for apxs2 in the specified directories AC_PATH_PROG(APXS2, apxs2,, /usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin) if test "x$APXS2" = "x"; then # Didn't find apxs2 in any of the specified directories. # Search for apxs instead. AC_PATH_PROG(APXS2, apxs,, /usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin) fi fi # Test if $APXS2 exists and is an executable. if test ! -x "$APXS2"; then # $APXS2 isn't a executable file. AC_MSG_ERROR([ Could not find apxs2. Please specify the path to apxs2 using the --with-apxs2=/full/path/to/apxs2 option. The executable may also be named 'apxs'. ]) fi # Replace any occurrences of @APXS2@ with the value of $APXS2 in the Makefile. AC_SUBST(APXS2) # Use environment varilable APXS2_OPTS to pass params to APXS2 command AC_ARG_VAR(APXS2_OPTS, [Additional command line options to pass to apxs2.]) # We need the curl library for HTTP callouts. PKG_CHECK_MODULES(CURL, libcurl) AC_SUBST(CURL_CFLAGS) AC_SUBST(CURL_LIBS) # We need OpenSSL for crypto and HTTPS callouts. PKG_CHECK_MODULES(OPENSSL, openssl) AC_SUBST(OPENSSL_CFLAGS) AC_SUBST(OPENSSL_LIBS) PKG_CHECK_MODULES(APR, [apr-1, apr-util-1]) AC_SUBST(APR_CFLAGS) AC_SUBST(APR_LIBS) # We need Jansson for JSON parsing. PKG_CHECK_MODULES(JANSSON, jansson) AC_SUBST(JANSSON_CFLAGS) AC_SUBST(JANSSON_LIBS) # cjose PKG_CHECK_MODULES(CJOSE, cjose) AC_SUBST(CJOSE_CFLAGS) AC_SUBST(CJOSE_LIBS) # PCRE PKG_CHECK_MODULES(PCRE, libpcre) AC_SUBST(PCRE_CFLAGS) AC_SUBST(PCRE_LIBS) # Redis AC_ARG_WITH([hiredis], [AS_HELP_STRING([--with-hiredis], [support Redis @<:@default=check@:>@])], [], [with_hiredis=yes]) AS_CASE(["$with_hiredis"], [yes], [if test "$HIREDIS_LIBS" == ""; then PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0]) ; else [HAVE_LIBHIREDIS=1] ; fi], [no], [HAVE_LIBHIREDIS=0], [PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0])]) AC_SUBST(HAVE_LIBHIREDIS) AC_SUBST(HIREDIS_CFLAGS) AC_SUBST(HIREDIS_LIBS) # JQ HAVE_LIBJQ=0 AC_ARG_WITH(jq, [ --with-jq=PATH location of your libjq installation]) if test -n "$with_jq" then JQ_CFLAGS="-I$with_jq/include" JQ_LIBS="-L$with_jq/lib -ljq" CPPFLAGS="$JQ_CFLAGS $CPPFLAGS" AC_CHECK_HEADERS([jq.h], , [HAVE_LIBJQ=0]) LDFLAGS="$JQ_LIBS $LDFLAGS" AC_CHECK_LIB([jq], [jq_init], [HAVE_LIBJQ=1], [HAVE_LIBJQ=0]) if test "x$have_jq" = "x0" ; then AC_MSG_WARN("cannot find library for -ljq.") fi fi AC_SUBST(HAVE_LIBJQ) AC_SUBST(JQ_CFLAGS) AC_SUBST(JQ_LIBS) # Create Makefile from Makefile.in AC_CONFIG_FILES([Makefile]) AC_OUTPUT mod_auth_openidc-2.3.3/Makefile.in0000644000076500000240000000457013200625410016722 0ustar hzandbeltstaff# Source files. mod_auth_openidc.c must be the first file. SRC=src/mod_auth_openidc.c \ src/cache/file.c \ src/cache/memcache.c \ src/cache/shm.c \ src/cache/common.c \ src/oauth.c \ src/proto.c \ src/config.c \ src/util.c \ src/authz.c \ src/session.c \ src/metadata.c \ src/jose.c \ src/parse.c \ src/pcre_subst.c \ ifeq (@HAVE_LIBHIREDIS@, 1) SRC += \ src/cache/redis.c REDIS_CFLAGS=-DUSE_LIBHIREDIS @HIREDIS_CFLAGS@ REDIS_LIBS=@HIREDIS_LIBS@ endif ifeq (@HAVE_LIBJQ@, 1) JQ_CFLAGS=-DUSE_LIBJQ @JQ_CFLAGS@ JQ_LIBS=@JQ_LIBS@ endif HDRS = \ $(JWT_HDRS) \ src/mod_auth_openidc.h \ src/jose.h \ src/parse.h \ src/cache/cache.h \ src/pcre_subst.h \ # Files to include when making a .tar.gz-file for distribution DISTFILES=$(SRC) \ $(HDRS) \ test/test.c \ test/test-cmd.c \ test/stub.c \ configure \ configure.ac \ Makefile.in \ autogen.sh \ INSTALL \ README.md \ AUTHORS \ DISCLAIMER \ auth_openidc.conf \ LICENSE.txt \ ChangeLog all: src/mod_auth_openidc.la CFLAGS=@OPENSSL_CFLAGS@ @CURL_CFLAGS@ @JANSSON_CFLAGS@ @CJOSE_CFLAGS@ @PCRE_CFLAGS@ $(REDIS_CFLAGS) $(JQ_CFLAGS) LIBS=@OPENSSL_LIBS@ @CURL_LIBS@ @JANSSON_LIBS@ @CJOSE_LIBS@ @PCRE_LIBS@ $(REDIS_LIBS) $(JQ_LIBS) src/mod_auth_openidc.la: $(SRC) $(HDRS) @APXS2@ @APXS2_OPTS@ -Wc,"-DNAMEVER=\"@NAMEVER@\" $(CFLAGS)" -Wl,"$(LIBS)" -Wc,-Wall -Wc,-g -c $(SRC) configure: configure.ac ./autogen.sh @NAMEVER@.tar.gz: $(DISTFILES) tar -c --transform="s#^#@NAMEVER@/#" -vzf $@ $(DISTFILES) test/test test/test-cmd: test/test.c test/stub.c src/mod_auth_openidc.la @APXS2@ @APXS2_OPTS@ $(CFLAGS) -Wl,"$(LIBS)" -Isrc -Wc,-Wall -Wc,-g -c -o $@ $@.c test/stub.c $(SRC:.c=.lo) @APR_LIBS@ test-compile: test/test test/test-cmd test: test-compile test/test .PHONY: install install: src/mod_auth_openidc.la @APXS2@ @APXS2_OPTS@ -i -n mod_auth_openidc src/mod_auth_openidc.la .PHONY: distfile distfile: @NAMEVER@.tar.gz .PHONY: clean clean: rm -f src/mod_auth_openidc.la rm -f src/*.o src/cache/*.o test/*.o rm -f src/*.lo src/cache/*.lo test/*.lo rm -f src/*.slo src/cache/*.slo test/*.slo rm -rf src/.libs/ src/cache/.libs/ test/.libs rm -rf test/test test/test-cmd .PHONY: distclean distclean: clean rm -f Makefile config.log config.status @NAMEVER@.tar.gz *~ \ build-stamp config.guess config.sub rm -rf debian/mod-auth_openidc rm -f debian/files .PHONY: fullclean fullclean: distclean rm -f configure aclocal.m4 mod_auth_openidc-2.3.3/autogen.sh0000755000076500000240000000007613137273467016677 0ustar hzandbeltstaff#!/bin/sh autoreconf --force --install rm -rf autom4te.cache/ mod_auth_openidc-2.3.3/INSTALL0000644000076500000240000000374613203103324015710 0ustar hzandbeltstaffPreferably you should use one of the pre-compiled binary packages, available for various platforms, see: https://github.com/zmartzone/mod_auth_openidc/wiki#11-where-can-i-get-binary-packages and proceed with the Configuration section below. If your platform is not supported or you want to run the latest code, you can build from source as described below. Installation from source ======================== You will require development headers and tools for the following dependencies: Apache (>=2.0) cjose (>=0.4.1) OpenSSL (>=0.9.8) (>=1.0.1 for Elliptic Curve support) Curl (>=?) Jansson (>=2.0) (JSON parser for C) pcre3 (>=?) (Regular Expressions support) pkg-config and if you want Redis support: hiredis (>=0.9.0) (Redis client for C) Configure, make and install with: (run ./autogen.sh first if you work straight from the github source tree) ./configure --with-apxs2=/opt/apache2/bin/apxs2 make make install Note that, depending on your distribution, apxs2 may be named apxs. FreeBSD users can use one of the following two options to install mod_auth_openidc: - To install the port: cd /usr/ports/www/mod_auth_openidc/ && make install clean - To add the package: pkg install ap24-mod_auth_openidc Configuration ============= Edit the configuration file for your web server. Depending on your distribution, it may be named '/etc/apache/httpd.conf' or something different. You need to add a LoadModule directive for mod_auth_openidc. This will look similar to this: LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so To find the full path to mod_auth_openidc.so, you may run: apxs2 -q LIBEXECDIR This will print the path where Apache stores modules. mod_auth_openidc.so will be stored in that directory. After you have added the LoadModule directive, you must add the configuration for mod_auth_openidc. For a quickstart doing so, see the provided samples in the README.md file. For an exhaustive overview of all configuration primitives, see: auth_openidc.conf mod_auth_openidc-2.3.3/README.md0000644000076500000240000002103213203103444016125 0ustar hzandbeltstaff[![Build Status](https://travis-ci.org/zmartzone/mod_auth_openidc.svg?branch=master)](https://travis-ci.org/zmartzone/mod_auth_openidc) mod_auth_openidc ================ *mod_auth_openidc* is an authentication/authorization module for the Apache 2.x HTTP server that functions as an **OpenID Connect Relying Party**, authenticating users against an OpenID Connect Provider. It can also function as an **OAuth 2.0 Resource Server**, validating OAuth 2.0 access tokens presented by OAuth 2.0 Clients. Overview -------- This module enables an Apache 2.x web server to operate as an [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) *Relying Party* (RP) to an OpenID Connect *Provider* (OP). It authenticates users against an OpenID Connect Provider, receives user identity information from the OP in a so called ID Token and passes the identity information (a.k.a. claims) in the ID Token to applications hosted and protected by the Apache web server. It can also be configured as an OAuth 2.0 *Resource Server* (RS), consuming bearer access tokens and validating them against an OAuth 2.0 Authorization Server, authorizing the Clients based on the validation results. The protected content and/or applications can be served by the Apache server itself or it can be served from elsewhere when Apache is configured as a Reverse Proxy in front of the origin server(s). By default the module sets the `REMOTE_USER` variable to the `id_token` `[sub]` claim, concatenated with the OP's Issuer identifier (`[sub]@[iss]`). Other `id_token` claims are passed in HTTP headers and/or environment variables together with those (optionally) obtained from the UserInfo endpoint. It allows for authorization rules (based on standard Apache `Require` primitives) that can be matched against the set of claims provided in the `id_token`/ `userinfo` claims. *mod_auth_openidc* supports the following specifications: - [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) Basic, Implicit, Hybrid and Refresh flows. - [OpenID Connect Dynamic Client Registration](http://openid.net/specs/openid-connect-registration-1_0.html) - [OpenID Provider Discovery](http://openid.net/specs/openid-connect-discovery-1_0.html) - [OAuth 2.0 Form Post Response Mode](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) - [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) - [OpenID Connect Session Management](http://openid.net/specs/openid-connect-session-1_0.html). See the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/Session-Management) for information on how to configure it. Alternatively the module can operate as an OAuth 2.0 Resource Server to an OAuth 2.0 Authorization Server, introspecting/validating bearer Access Tokens conforming to [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662) (or similar), or verifiying them locally if they are JWTs. The `REMOTE_USER` variable setting, passing claims in HTTP headers and authorization based on `Require` primitives works in the same way as described for OpenID Connect above. See the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server) for information on how to configure it. For an exhaustive description of all configuration options, see the file `auth_openidc.conf` in this directory. This file can also serve as an include file for `httpd.conf`. How to Use It ------------- ### OpenID Connect SSO with Google+ Sign-In Sample configuration for using Google as your OpenID Connect Provider running on `www.example.com` and `https://www.example.com/example/redirect_uri` registered as the *redirect_uri* for the client through the Google API Console. You will also have to enable the `Google+ API` under `APIs & auth` in the [Google API console](https://console.developers.google.com). ```apache OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration OIDCClientID OIDCClientSecret OIDCRedirectURI https://www.example.com/example/redirect_uri OIDCCryptoPassphrase AuthType openid-connect Require valid-user ``` Note if you want to securely restrict logins to a specific Google Apps domain you would not only add the `hd=` setting to the `OIDCAuthRequestParams` primitive for skipping the Google Account Chooser screen, but you must also ask for the `email` scope using `OIDCScope` and use a `Require claim` authorization setting in the `Location` primitive similar to: ```apache OIDCScope "openid email" Require claim hd: ``` The above is an authorization example of an exact match of a provided claim against a string value. For more authorization options see the [Wiki page on Authorization](https://github.com/zmartzone/mod_auth_openidc/wiki/Authorization). ### Quickstart with a generic OpenID Connect Provider 1. install and load `mod_auth_openidc.so` in your Apache server 1. configure your protected content/locations with `AuthType openid-connect` 1. set `OIDCRedirectURI` to a "vanity" URL within a location that is protected by mod_auth_openidc 1. register/generate a Client identifier and a secret with the OpenID Connect Provider and configure those in `OIDCClientID` and `OIDCClientSecret` respectively 1. and register the `OIDCRedirectURI` as the Redirect or Callback URI with your client at the Provider 1. configure `OIDCProviderMetadataURL` so it points to the Discovery metadata of your OpenID Connect Provider served on the `.well-known/openid-configuration` endpoint 1. configure a random password in `OIDCCryptoPassphrase` for session/state encryption purposes ```apache LoadModule auth_openidc_module modules/mod_auth_openidc.so OIDCProviderMetadataURL /.well-known/openid-configuration OIDCClientID OIDCClientSecret OIDCRedirectURI https:///secure/redirect_uri OIDCCryptoPassphrase AuthType openid-connect Require valid-user ``` For details on configuring multiple providers see the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/Multiple-Providers). ### PingFederate OAuth 2.0 Resource Server Example config for using PingFederate as your OAuth 2.0 Authorization server, based on the OAuth 2.0 PlayGround configuration and doing claims-based authorization, using RFC 7662 compliant Token Introspection. ```apache # remote validation OIDCOAuthIntrospectionEndpoint https://localhost:9031/as/introspect.oauth2 OIDCOAuthIntrospectionEndpointAuth client_secret_basic OIDCOAuthRemoteUserClaim Username OIDCOAuthSSLValidateServer Off OIDCOAuthClientID rs_client OIDCOAuthClientSecret 2Federate AuthType oauth20 Require claim client_id:ro_client #Require claim scope~\bprofile\b ``` For details and additional options on the OAuth 2.0 Resource Server setup see the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server). ### Quickstart with a generic OAuth 2.0 Resource Server Using "local" validation of JWT bearer tokens: 1. install and load `mod_auth_openidc.so` in your Apache server 1. configure your protected APIs/locations with `AuthType oauth20` and `Require claim` directives to restrict access to specific clients/scopes/claims/resource-owners 1. configure local or remote bearer token validation following the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server) ```apache # local validation OIDCOAuthVerifySharedKeys plain## AuthType oauth20 Require claim sub: ``` Support ------- See the Wiki pages with Frequently Asked Questions at: https://github.com/zmartzone/mod_auth_openidc/wiki There is a Google Group/mailing list at: [mod_auth_openidc@googlegroups.com](mailto:mod_auth_openidc@googlegroups.com) The corresponding forum/archive is at: https://groups.google.com/forum/#!forum/mod_auth_openidc For commercial support and consultancy you can contact: [info@zmartzone.eu](mailto:info@zmartzone.eu) Any questions/issues should go to the mailing list or the primary author [hans.zandbelt@zmartzone.eu](mailto:hans.zandbelt@zmartzone.eu). The Github issues tracker should be used only for bugs reports and feature requests. Disclaimer ---------- *This software is open sourced by ZmartZone IAM. For commercial support you can contact [ZmartZone IAM](https://www.zmartzone.eu) as described above.* mod_auth_openidc-2.3.3/AUTHORS0000644000076500000240000000340513202535004015722 0ustar hzandbeltstaffThe primary author of mod_auth_openidc is: Hans Zandbelt Thanks to the following people for contributing to mod_auth_openidc by reporting bugs, providing fixes, suggesting useful features or other: Dániel SÜTTŐ Roland Hedberg Bill Simon Jim Fox Martin Srom Dejan Latinovic Hiroyuki Wada Gunnar Scherf Terrence Fleury Jeremy Archer Forkbomber Kanthi Vaidya szakharchenko John Bradley Stefano Vercelli David Bernick Joseph Bester Daniel Pfile Rebecka Gulliksson Ryan Kelly John R. Dennis steve-dave glatzert Amit Joshi Andy Curtis solsson drdivano AliceWonderMiscreations Wouter Hund Hans Keeler Moritz Schlarb remi-cc hihellobolke Horatiu Eugen Vlad cristichiru mod_auth_openidc-2.3.3/DISCLAIMER0000644000076500000240000000242713137273467016237 0ustar hzandbeltstaff/*************************************************************************** * Copyright (C) 2014-2017 Ping Identity Corporation * All rights reserved. * * Ping Identity Corporation * 1099 18th St Suite 2950 * Denver, CO 80202 * 303.468.2900 * http://www.pingidentity.com * * DISCLAIMER OF WARRANTIES: * * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ mod_auth_openidc-2.3.3/auth_openidc.conf0000644000076500000240000013250113203320522020161 0ustar hzandbeltstaff######################################################################################## # # Common Settings # ######################################################################################## # (Mandatory) # The redirect_uri for this OpenID Connect client; this is a vanity URL # that must ONLY point to a path on your server protected by this module # but it must NOT point to any actual content that needs to be served. # You can use a relative URL like /protected/redirect_uri if you want to # support multiple vhosts that belong to the same security domain in a dynamic way #OIDCRedirectURI https://www.example.com/protected/redirect_uri # (Mandatory) # Set a password for crypto purposes, this is used for: # - encryption of the (temporary) state cookie # - encryption of cache entries, that may include the session cookie, see: OIDCCacheEncrypt and OIDCSessionType # Note that an encrypted cache mechanism can be shared between servers if they use the same OIDCCryptoPassphrase #OIDCCryptoPassphrase # # All other entries below this are optional though some may be required in a # particular setup e.g. OAuth 2.0 Resource Server vs. OpenID Connect Relying Party # # When using multiple OpenID Connect Providers, possibly combined with Dynamic Client # Registration and account-based OP Discovery. # Specifies the directory that holds metadata files (must be writable for the Apache process/user). # When not specified, it is assumed that we use a single statically configured provider as # described under the section "OpenID Connect Provider" below, most likely using OIDCProviderMetadataURL. #OIDCMetadataDir /var/cache/apache2/mod_auth_openidc/metadata ######################################################################################## # # OpenID Connect Provider # # For configuration of a single static provider, not using OpenID Connect Provider Discovery. # ######################################################################################## # URL where OpenID Connect Provider metadata can be found (e.g. https://accounts.google.com/.well-known/openid-configuration) # The obtained metadata will be cached and refreshed every 24 hours. # If set, individual entries below will not have to be configured but can be used to add # extra entries/endpoints to settings obtained from the metadata. # If OIDCProviderMetadataURL is not set, the entries below it will have to be configured for a single # static OP configuration or OIDCMetadataDir will have to be set for configuration of multiple OPs. #OIDCProviderMetadataURL # OpenID Connect Provider issuer identifier (e.g. https://localhost:9031 or https://accounts.google.com) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderIssuer # OpenID Connect Provider Authorization Endpoint URL (e.g. https://localhost:9031/as/authorization.oauth2) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderAuthorizationEndpoint # OpenID Connect Provider JWKS URL (e.g. https://localhost:9031/pf/JWKS) # i.e. the URL on which the signing keys for this OP are hosted, in JWK formatting # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderJwksUri # OpenID Connect Provider Token Endpoint URL (e.g. https://localhost:9031/as/token.oauth2) # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderTokenEndpoint # Authentication method for the OpenID Connect Provider Token Endpoint. # One of "client_secret_basic", "client_secret_post", "client_secret_jwt" or "private_key_jwt". # When "private_key_jwt" is used, OIDCPrivateKeyFiles and OIDCPublicKeyFiles must have been set. # When not defined the default method from the specification is used, i.e. "client_secret_basic". # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. # NB: this can be overridden for dynamic client registration on a per-OP basis in the .conf file using the key: token_endpoint_auth #OIDCProviderTokenEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt] # Extra parameters that need to be passed in the POST request to the Token Endpoint. # Parameter names and values need to be provided in URL-encoded form. # When not defined no extra parameters will be passed. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_params #OIDCProviderTokenEndpointParams =[&=]* # OpenID Connect Provider UserInfo Endpoint URL (e.g. https://localhost:9031/idp/userinfo.openid) # When not defined no claims will be resolved from such endpoint. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderUserInfoEndpoint # OpenID OP Check Session iFrame URL, for Session Management purposes. # When not defined, no Session Management will be applied. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderCheckSessionIFrame # OpenID OP End Session Endpoint URL, for Single Logout (Session Management) purposes. # When not defined, no logout to the OP will be performed. # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderEndSessionEndpoint # Extra JSON parameters that need to be passed in the registration request to the Registration Endpoint. # This settings serves as a default value for multiple OPs only. # Parameter names and values need to be provided in JSON form and will be merged in to the request. # When not defined no extra parameters will be passed. # NB: this can be overridden on a per-OP basis in the .conf file using the key: registration_endpoint_params #OIDCProviderRegistrationEndpointJson # Define the OpenID Connect scope that is requested from the OP (eg. "openid email profile"). # When not defined, the bare minimal scope "openid" is used. # NB: multiple scope values must be enclosed in a single pair of double quotes # NB: this can be overridden on a per-OP basis in the .conf file using the key: scope #OIDCScope "" # Extra parameters that will be sent along with the Authorization Request. # These must be URL-query-encoded as in: "display=popup&prompt=consent" or # specific for Google's implementation: "approval_prompt=force". # This is used against a statically configured (single) OP or serves as the default for discovered OPs. # As an alternative to this option, one may choose to add the parameters as # part of the URL set in OIDCProviderAuthorizationEndpoint or "authorization_endpoint" # in the .provider metadata (though that would not work with Discovery OPs). # The default is to not add extra parameters. # NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_params #OIDCAuthRequestParams # Require a valid SSL server certificate when communicating with the OP. # (i.e. on token endpoint, UserInfo endpoint and Dynamic Client Registration endpoint) # When not defined, the default value is "On". # NB: this can be overridden on a per-OP basis in the .conf file using the key: ssl_validate_server #OIDCSSLValidateServer [On|Off] # The refresh interval in seconds for the claims obtained from the userinfo endpoint # When not defined the default is 0, i.e. the claims are retrieved only at session creation time. # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_refresh_interval #OIDCUserInfoRefreshInterval # The refresh interval in seconds for the JWKs key set obtained from the jwk_uri. # When not defined the default is 3600 seconds. # NB: this can be overridden on a per-OP basis in the .conf file using the key: jwks_refresh_interval #OIDCJWKSRefreshInterval # Defines the way in which the access token will be presented to the userinfo endpoint # "authz_header" means that the token will be presented in an "Authorization: Bearer" header using HTTP GET # "post_param" means that the token will be presented a form-encoded POST parameter using HTTP POST # When not defined the default is "authz_header". # NB: this can be overrridden on a per-OP basis in the .conf file using the key: userinfo_token_method #OIDCUserInfoTokenMethod [authz_header|post_param] # Defines the HTTP method used to pass the parameters in the Authentication Request to the Authorization Endpoint. # "GET" means that the parameters will be passed as query parameters in an HTTP GET # "POST" means that the parameters will be passed as form-post parameters in an HTTP POST # When not defined the default is "GET". # NB: this can be overrridden on a per-OP basis in the .conf file using the key: auth_request_method # OIDCProviderAuthRequestMethod [ GET | POST ] ######################################################################################## # # OpenID Connect Client # # Settings used by the client in communication with the OpenID Connect Provider(s), # i.e. in Authorization Requests, Dynamic Client Registration and UserInfo Endpoint access. # These settings are used when a single static provider is configured and serve as defaults # when multiple providers are configured. # ######################################################################################## # The response type (or OpenID Connect Flow) used (this serves as default value for discovered OPs too) # When not defined the "code" response type is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: response_type #OIDCResponseType ["code"|"id_token"|"id_token token"|"code id_token"|"code token"|"code id_token token"] # The response mode used (this serves as default value for discovered OPs too) # When not defined the default response mode for the requested flow (OIDCResponseType) is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: response_mode #OIDCResponseMode [fragment|query|form_post] # Only used for a single static provider has been configured, see below in OpenID Connect Provider. # Client identifier used in calls to the statically configured OpenID Connect Provider. #OIDCClientID # Only used for a single static provider has been configured, see below in OpenID Connect Provider. # Client secret used in calls to the statically configured OpenID Connect Provider. # (not used/required in the Implicit Client Profile, i.e. when OIDCResponseType is "id_token") #OIDCClientSecret # Filename with the PEM-formatted client certificate used to authenticate the Client in calls to the # token endpoint of the OAuth 2.0 Authorization server. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_cert #OIDCClientTokenEndpointCert # Filename with the PEM-formatted private key that belongs to the client certificate used to authenticate the # Client in calls to the token endpoint of the OAuth 2.0 Authorization server. # NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_key #OIDCClientTokenEndpointKey # The client name that the client registers in dynamic registration with the OP. # When not defined, no client name will be sent with the registration request. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_name #OIDCClientName # The contacts that the client registers in dynamic registration with the OP. # Must be formatted as e-mail addresses by specification. # Single value only; when not defined, no contact e-mail address will be sent with the registration request. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_contact #OIDCClientContact # The PKCE method used (this serves as default value for discovered OPs too) # When not defined PKCE is not used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: pkce_method #OIDCPKCEMethod [plain|S256|referred_tb] # The OpenID Connect Bound Authentication policy used, # see: http://openid.net/specs/openid-connect-token-bound-authentication-1_0.html # "disabled": no referred token binding will be requested from the User Agent upon redirection to the OP # "optional": referred token binding will be requested, the "cnf["tbh"]" claim is optional on return # "required": referred token binding will be requested, the "cnf["tbh"]" claim must be present when the Client supports Token Binding # "enforced": referred token binding will be requested, the "cnf["tbh"]" claim must be present and the User Agent must support Token Binding #OIDCTokenBindingPolicy [disabled|optional|required|enforced] # (used only in dynamic client registration) # Define the Client JWKs URL (e.g. https://localhost/protected/?jwks=rsa)") that will be # used during client registration to point to the JWK set with public keys for this client. # If not defined the default ?jwks=rsa will be used, on which a JWK set # is automatically published based on the OIDCPublicKeyFiles setting so normally you don't # need to touch this unless this client is on a (test) host that is not reachable from the internet. # NB: this can be overridden on a per-OP basis in the .conf file using the key: client_jwks_uri #OIDCClientJwksUri # (used only in dynamic client registration) # The algorithm that the OP should use to sign the id_token. # When not defined the default that the OP should use by spec is RS256. # (ES??? algorithms only supported when using OpenSSL >= 1.0) # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_signed_response_alg #OIDCIDTokenSignedResponseAlg [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512] # (used only in dynamic client registration) # The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token. # When not defined the default (by spec) is that the OP does not encrypt the id_token. # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_alg #OIDCIDTokenEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP] # (used only in dynamic client registration) # The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key. # If OIDCIDTokenEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256. # When OIDCIDTokenEncryptedResponseEnc is included, OIDCIDTokenEncryptedResponseAlg MUST also be provided. # (A256GCM algorithm only supported when using OpenSSL >= 1.0.1) # NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_enc #OIDCIDTokenEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM] # (used only in dynamic client registration) # The algorithm that the OP should use to sign the UserInfo response # When not defined the default (by spec) is that the OP does not sign the response. # (ES??? algorithms only supported when using OpenSSL >= 1.0) # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_signed_response_alg #OIDCUserInfoSignedResponseAlg RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512] # (used only in dynamic client registration) # The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response. # When not defined the default (by spec) is that the OP does not encrypt the response. # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_alg #OIDCUserInfoEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP] # (used only in dynamic client registration) # The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key # If OIDCUserInfoEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256. # When OIDCUserInfoEncryptedResponseEnc is included, OIDCUserInfoEncryptedResponseAlg MUST also be provided. # (A256GCM algorithm only supported when using OpenSSL >= 1.0.1) # NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_enc #OIDCUserInfoEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM] ######################################################################################## # # OAuth 2.0 Resource Server Settings # # Used when this module functions as a Resource Server against an OAuth 2.0 Authorization # Server, introspecting/validating bearer Access Tokens. # ######################################################################################## # (Mandatory when introspecting opaque access tokens, Optional when performing local JWT access token validation) # OAuth 2.0 Authorization Server token introspection endpoint (e.g. https://localhost:9031/as/token.oauth2) #OIDCOAuthIntrospectionEndpoint # Client identifier used in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthClientID # Client secret used in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthClientSecret # Authentication method for the OAuth 2.0 Authorization Server introspection endpoint, # Used to authenticate the client to the introspection endpoint e.g. with a client_id/client_secret # when OIDCOAuthClientID and OIDCOAuthClientSecret have been set and "client_secret_basic" or "client_secret_post" # has been configured. # When "private_key_jwt" is used, OIDCPrivateKeyFiles and OIDCPublicKeyFiles must have been set. # When not defined "client_secret_basic" is used. #OIDCOAuthIntrospectionEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt] # Some OP do not accept basic or post, only bearer tokens in the Authorization header. # Specify here a static token to be used for authorizing the call to the introspection endpoint. # If empty, the introspected token will be used for authorization as well. # If unset, one of the methods specified by OIDCOAuthIntrospectionEndpointAuth will be used. #OIDCOAuthIntrospectionClientAuthBearerToken [ a-static-bearer-token | ] # Filename that contains the PEM-formatted client certificate used to authenticate the # caller in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthIntrospectionEndpointCert # Filename that contains the PEM-formatted private key that belongs to the client certificate used # to authenticate the caller in token introspection calls to the OAuth 2.0 Authorization server. #OIDCOAuthIntrospectionEndpointKey # Define the HTTP method to use for the introspection call. Must be GET or POST. # When not defined the default is POST. #OIDCOAuthIntrospectionEndpointMethod [POST|GET] # 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 =[&=]* # 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 # 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] # Define the interval in seconds after which a cached and introspected access token needs # to be refreshed by introspecting (and validating) it again against the Authorization Server. # (can be configured on a per-path basis) # When not defined the value is 0, which means it only expires after the `exp` (or alternative, # see OIDCOAuthTokenExpiryClaim) hint as returned by the Authorization Server #OIDCOAuthTokenIntrospectionInterval # 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] # The symmetric shared key(s) that can be used for local JWT access token validation. # NB: this is one or more key tuples where a key tuple consists of: # plain|b64|hex#[]# # When not defined, no access token validation with shared keys will be performed. # Examples: # - a plaintext secret and a key identifier (kid) # plain#1#mysecret # - a base64 encoded secret, no key identifier provided # b64##AF515DE== # - a hex encoded secret, no key identifier provided # hex##ede012 #OIDCOAuthVerifySharedKeys ([plain|b64|hex#][#])+ # 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. # NB: this is one or more key tuples where a key tuple consists of: # [#] # and the key identifier part is optional. # When not defined, no access token validation with statically configured certificates will be performed. #OIDCOAuthVerifyCertFiles ([#])+ # The JWKs URL on which the Authorization Server 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 # 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 ^(.*)@ # # An optional 3rd parameter can be added that would contain string with number backrefrences. # Backrefrences must be in the form $1, $2.. etc. # E.g. to extract username in the form DOMAIN\userid from e-mail style address you may use # ^(.*)@([^.]+)\..+$ $2\\$1 #OIDCOAuthRemoteUserClaim [] # Define the way(s) in which bearer OAuth 2.0 access tokens can be passed to this Resource Server. # Must be one or several of: # "header" : an "Authorization: bearer" header # "post" : an HTTP Post parameter called "access_token" # "query" : as an HTTP query parameter called "access_token" # "cookie" : as a cookie header called "PA.global" or using the name specified after ":" # When not defined the default "header" is used. #OIDCOAuthAcceptTokenAs [header|post|query|cookie[:]+ ######################################################################################## # # Cookie Settings # ######################################################################################## # Define the cookie path for the "state" and "session" cookies. # When not defined the default is a server-wide "/". #OIDCCookiePath # 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 using a relative OIDCRedirectURI this setting should most probably empty. # When not defined the default is the server hostname that is currently accessed. #OIDCCookieDomain # Define the cookie name for the session cookie. # When not defined the default is "mod_auth_openidc_session". #OIDCCookie # OpenID Connect session cookie chunk size. # When using "OIDCSessionType client-cookie" the session cookie may become quite large if a lot of session # data needs to be stored, typically the size depends on the "scopes" of information you request. To work # around cookie size limitations for most web browsers (usually 4096 bytes), the "client-cookie" will be split # over a number of "chunked" cookies if the resulting session data is over a certain number of bytes, # If you want to prevent splitting the session cookie regardless of its size, set the value to 0. # When not defined the default chunk size is 4000 bytes #OIDCSessionCookieChunkSize 4000 # Defines whether the HttpOnly flag will be set on cookies. # When not defined the default is On. #OIDCCookieHTTPOnly [On|Off] # Defines whether the SameSite flag will be set on cookies. # When On the following will apply: # state cookie: Lax # session cookie: first time set Lax, updates (e.g. after inactivity timeout) Strict # x_csrf discovery: Strict: # When not defined the default is Off. #OIDCCookieSameSite [On|Off] # 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 []+ # Specify the names of cookies to strip from the incoming request so they are not passed # on to the target application(s). This may prevent a large set of chunked session cookies to # be sent to the backend. In that case you'd set it to (when using the default OIDCCookie setting): # mod_auth_openidc_session mod_auth_openidc_session_chunks mod_auth_openidc_session_0 mod_auth_openidc_session_1 # When not defined, no cookies are stripped. #OIDCStripCookies []+ ######################################################################################## # # Session Settings (only relevant in an OpenID Connect Relying Party setup) # ######################################################################################## # Interval in seconds after which the session will be invalidated when no interaction has occurred. # When not defined, the default is 300 seconds. #OIDCSessionInactivityTimeout # 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 # OpenID Connect session storage type. # "server-cache" server-side caching storage. # "client-cookie" uses browser-side sessions stored in a cookie; see also OIDCSessionCookieChunkSize next # A suffix ":persistent" can be added if you want to use a persistent cookie that survives browser restarts # instead of a session cookie that is tied to the lifetime of the browser session. # The "expires" value of the persistent cookie is controlled by the OIDCSessionInactivityTimeout setting. # When not defined the default "server-cache" is used. #OIDCSessionType server-cache[:persistent]|client-cookie[:persistent] # Fallback to "OIDCSessionType client-cookie" when "OIDCSessionType server-cache" is set and the primary # cache mechanism (e.g. memcache or redis) fails. Note that this will come at a cost of: # a) performance # 1) since on each subsequent request the primary cache will still be polled and # failback will happen as soon as the primary cache is available again # 2) information other than sessions cannot be cached, e.g. resolved access tokens or metadata; see: OIDCCacheType # b) security, since nonce's and jti's are not cached, see: OIDCCacheType # c) (prototype) functionality, since request_uri's won't work anymore # When not defined the default is "Off". #OIDCSessionCacheFallbackToCookie [On|Off] ######################################################################################## # # Cache Settings # ######################################################################################## # 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 # g) temporary state associated with Request URI's # 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]] # Indicate whether data in the cache backend should be encrypted. # When not defined the default is "Off" for the "shm" backend and "On" for all other cache backends #OIDCCacheEncrypt [On|Off] # 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 # 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 # 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 # 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 # Required when using OIDCCacheType "memcache": # Specifies the memcache servers used for caching as a space separated list of [:] tuples. #OIDCMemCacheServers "([:])+" # Required if Redis support is compiled in and when using OIDCCacheType "redis": # Specifies the Redis server used for caching as a [:] tuple. #OIDCRedisCacheServer [:] # Password to be used if the Redis server requires authentication: http://redis.io/commands/auth # When not specified, no authentication is performed. #OIDCRedisCachePassword ######################################################################################## # # Advanced Settings # ######################################################################################## # Defines an external OP Discovery page. That page will be called with: # ?oidc_callback= # additional parameters may be added, a.o. `target_link_uri`, `x_csrf` and `method`. # # An Issuer selection can be passed back to the callback URL as in: # ?iss=[${issuer}|${domain}|${e-mail-style-account-name}][parameters][&login_hint=][&scopes=][&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. # [parameters] contains the additional parameters that were passed in on the discovery request (e.g. target_link_uri=&x_csrf=&method=&scopes=) # # When not defined the bare-bones internal OP Discovery page is used. #OIDCDiscoverURL # Template used to display error messages. # The template must be prepared to take two strings, an error title and a more details error description, # both HTML encoded values, in that order and referenced by (C-style) "%s", e.g.

Message:%s

Description:%s

. # A minimal example that posts error+detail to another webpage: # #
# # #
# # When not defined a bare-bones internal template is used. #OIDCHTMLErrorTemplate # 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 # 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 # Define the OpenID Connect scope(s) that is requested from the OP (eg. "admin edit") # on a per-path basis in addition to the per-provider configured scopes (OIDCScope). # NB: multiple scope values must be enclosed in a single pair of double quotes #OIDCPathScope "" # Extra parameters that will be sent along with the Authorization Request. # These must be URL-query-encoded as in: "display=popup&prompt=consent". # NB: since version 2.3.0 this can be configured on a per-path basis across all configured Providers. # The default is to not add extra parameters. #OIDCPathAuthRequestParams # 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. # You can also prefix with a JWK key identifier to manually override the automatically # generated "kid" that will be used for this key in the JWKs derived from this certificate and # published at OIDCClientJwksUri. #OIDCPublicKeyFiles ([#])+ # 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 ([#])+ # 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 # The prefix to use when setting claims (openid-connect or oauth20) in the HTTP headers/environment variables. # This prefix should not be set to "" except when combined with OIDCWhiteListedClaims to maintain a secure setup. # When not defined, the default "OIDC_CLAIM_" is used. #OIDCClaimPrefix # 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 # 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 ^(.*)@ # # An optional 3rd parameter can be added that would contain string with number backrefrences. # Backrefrences must be in the form $1, $2.. etc. # E.g. to extract username in the form DOMAIN\userid from e-mail style address you may use # ^(.*)@([^.]+)\..+$ $2\\$1 #OIDCRemoteUserClaim [@] [] # Define the way(s) in which the id_token contents are passed to the application according to OIDCPassClaimsAs. # 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]+ # Define the way(s) in which the claims resolved from the userinfo endpoint are passed to the application according to OIDCPassClaimsAs. # Must be one or several of: # "claims" : the userinfo claims are passed in individual headers/environment variables # "json" : a self-contained userinfo JSON object is passed in the "OIDC_userinfo_json" header/environment variable # "jwt" : a signed/encrypted JWT (if available!) optionally resolved from the userinfo endpoint is passed in the "OIDC_userinfo_jwt" header/environment variable # When not defined the default "claims" is used. #OIDCPassUserInfoAs [claims|json|jwt]+ # Define the way in which the claims and tokens are passed to the application environment: # "none": no claims/tokens are passed # "environment": claims/tokens are passed as environment variables # "headers": claims/tokens are passed in headers (also useful in reverse proxy scenario's) # "both": claims/tokens are passed as both headers as well as environment variables (default) # When not defined the default is "both" # The access token is passed in OIDC_access_token; the access token expiry is passed in OIDC_access_token_expires. # The refresh token is only passed in OIDC_refresh_token if enabled for that specific directory/location (see: OIDCPassRefreshToken) #OIDCPassClaimsAs [none|headers|environment|both] # 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 or OIDCOAuthRemoteUserClaim. # 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 # 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 # 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 # 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 # 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] # Specify an outgoing proxy for your network. # When not defined no outgoing proxy is used. #OIDCOutgoingProxy [:] # 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. # "410" means that HTTP 410 Gone 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|410] # Defines the action to be taken when an unauthorized request is made i.e. the user is authenticated but # does not meet the `Require claim *:*` directives or similar. # "401" means that HTTP 401 Unauthorized is returned. # "403" means that HTTP 403 Forbidded is returned: NB: for Apache 2.4 this is controlled by the AuthzSendForbiddenOnFailure directive! # "auth" means that the user is redirected to the OpenID Connect Provider or Discovery page. # Useful in Location/Directory/Proxy path contexts that need to do stepup authentication # When not defined the default "401" is used. #OIDCUnAutzAction [401|403|auth] # Indicates whether POST data will be preserved across authentication requests (and discovery in case of multiple OPs). # Preservation is done via HTML 5 local storage. Note that this can lead to private data exposure on shared terminals, # that is why the default is "Off". Can be configured on a per Directory/Location basis. #OIDCPreservePost [On|Off] # Indicates whether the refresh token will be passed to the application in a header/environment variable, according # to the OIDCPassClaimsAs directive. # Can be configured on a per Directory/Location basis. The default is "Off". #OIDCPassRefreshToken [On|Off] # Request Object/URI settings expressed as a string that is a "double-quote-escaped" JSON object. For example: # "{ \"copy_from_request\": [ \"claims\", \"response_type\", \"response_mode\", \"login_hint\", \"id_token_hint\", \"nonce\", \"state\", \"redirect_uri\", \"scope\", \"client_id\" ], \"static\": { \"some\": \"value\", \"some_nested\": { \"some_array\": [ 1,2,3] } }, \"crypto\": { \"sign_alg\": \"HS256\", \"crypt_alg\": \"A256KW\", \"crypt_enc\": \"A256CBC-HS512\" }, \"url\": \"https://www.zmartzone.eu/protected/\", \"request_object_type\" : \"request\" }" # Parameters: # copy_from_request (array) : array of query parameter names copied from request # copy_and_remove_from_request (array) : array of parameter names copied from request and removed as query parameter # static (object) : parameter value is merged to the request object # crypto (object) : defines cryptography used to create request object # sign_alg (string) : algorithm used to sign request object (JWS alg parameter) # crypt_alg (string) : algorithm used to encrypt CEK of request object (JWE alg parameter) # crypt_enc (string) : algorithm used to encrypt request object (JWE enc parameter) # url (string) : use this url instead of redirect_uri for request_uri # request_object_type (string) : parameter used for sending authorization request object # "request_uri" (default) or "request" # NB: this can be overridden on a per-OP basis in the .conf file using the key: request_object #OIDCRequestObject # Provider metadata refresh interval for the metadata in a multi-provider setup (with OIDCMetadataDir). # When not defined the default is 0 seconds, i.e. it is never refreshed. # Also used in a single provider setup with OIDCProviderMetadatURL but 0 then means the default of 1 day. #OIDCProviderMetadataRefreshInterval # Define the data that will be returned upon calling the info hook (i.e. ?info=json) # iat (int) : Unix timestamp indicating when this data was created # access_token (string) : the access token # access_token_expires (int) : the Unix timestamp which is a hint about when the access token will expire (as indicated by the OP) # id_token (object) : the claims presented in the ID token # userinfo (object) : the claims resolved from the UserInfo endpoint # refresh_token (string) : the refresh token (if returned by the OP) # session (object) : (for debugging) mod_auth_openidc specific session data such as "remote user", "session expiry", "session id" and a "state" object # When not defined the session hook will not return any data but a HTTP 404 #OIDCInfoHook [iat|access_token|access_token_expires|id_token|userinfo|refresh_token|session]+ # Specify claims that should be removed from the userinfo and/or id_token before storing them in the session. # Note that OIDCBlackListedClaims takes precedence over OIDCWhiteListedClaims # When not defined no claims are blacklisted and all claims are stored except when OIDCWhiteListedClaims is used. #OIDCBlackListedClaims []+ # Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded). # Note that OIDCBlackListedClaims takes precedence over OIDCWhiteListedClaims # When not defined no claims are whitelisted and all claims are stored except when blacklisted with OIDCBlackListedClaims. #OIDCWhiteListedClaims []+ mod_auth_openidc-2.3.3/LICENSE.txt0000644000076500000240000002613613137273467016526 0ustar hzandbeltstaff 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-2.3.3/ChangeLog0000644000076500000240000011273313203320671016434 0ustar hzandbeltstaff11/16/2017 - add support for passing userinfo as a JSON object or JWT; see #311 - release 2.3.3 11/13/2017 - add support for authentication to the introspection endpoint with a bearer token using OIDCOAuthIntrospectionClientAuthBearerToken; thanks @cristichiru - bump to 2.3.3rc3 11/08/2017 - address a number of static code analysis issues - bump to 2.3.3rc2 10/10/2017 - avoid crash when no scheme is set on OIDCProviderMetadataURL; closes #303; thanks @iconoeugen - bump to 2.3.3rc1 10/6/2017 - avoid crash when no OIDCOAuthClientID is set for remote access token validation - don't enforce "iat" slack checks on locally validaed JWT access tokens - bump to 2.3.3rc0 09/18/2017 - release 2.3.2 09/11/2017 - fix "graceful" restart for shm/redis cache backends; see #296 - bump to 2.3.2rc8 09/05/2017 - optionally remove request object parameters from the authorization request URL with "copy_and_remove_from_request"; see #294 - bump to 2.3.2rc7 08/29/2017 - properly support JSON boolean values in metadata .conf files - add regex substitution for *RemoteUserClaim; thanks @hihellobolke - bump to 2.3.2rc6 08/27/2017 - add issuer specific redirect URI option ("issuer_specific_redirect_uri") for multi-provider setups to mitigate IDP mixup - bump to 2.3.2rc5 08/20/2017 - fix public clients; add endpoint authentication method "none" - bump to 2.3.2rc4 08/02/2017 - update experimental token binding support to https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 and use header names prefixed with "Sec-"; depends on mod_token_binding >= 0.3.4 now - bump to 2.3.2rc3 08/01/2017 - don't abort when mutex operations fail - printout textual descriptions of errors returned by mutex operations - bump to 2.3.2rc2 07/28/2017 - fix issue with the combination of shared memory (shm) cache and using encryption (OIDCCacheEncrypt On) where the cache value would be corrupted after the first (successful) retrieval - bump to 2.3.2rc1 07/27/2017 - support paths that are relative to the Apache root dir for: OIDCHTMLErrorTemplate, OIDCPublicKeyFiles, OIDCPrivateKeyFiles, OIDCOAuthVerifyCertFiles, OIDCClientTokenEndpointCert, OIDCClientTokenEndpointKey, OIDCOAuthIntrospectionEndpointCert and OIDCOAuthIntrospectionEndpointKey - bump to 2.3.2rc0 07/19/2017 - handle multiple values in X-Forwarded-* headers as to better support chains of reverse proxies in front of mod_auth_openidc - log request headers in oidc_util_hdr_in_get - release 2.3.1 07/13/2017 - remove A128GCM/A192GCM from the supported algorithms in docs/auth_openidc.conf because cjose doesn't support A128GCM and A192GCM (yet) - bump to 2.3.1rc5 07/09/2017 - refactor oidc_get_current_url_port so that it assumes the default port when X-Forwarded-Proto has been set; closes #282 and may address #278 - bump to 2.3.1rc4 07/07/2017 - use the defined name (`Provided-Token-Binding-ID`) for the provided token binding ID HTTP header see: https://tools.ietf.org/html/draft-campbell-tokbind-ttrp-00#section-2.1 depends on mod_token_binding >= 0.3.0 now - bump to 2.3.1rc3 06/29/2017 - support sending the authentication request via HTTP POST through HTML/Javascript autosubmit - bump to 2.3.1rc2 06/28/2017 - support private_key_jwt and client_secret_jwt as client authentication methods for token introspection - bump to 2.3.1rc1 06/22/2017 - fix bug where token_endpoint_auth set to private_key_jwt would fail to provide the credential if client_secret wasn't set - bump to 2.3.1rc0 06/13/2017 - release 2.3.0 06/07/2017 - fix file cache backend: allow caching of non-filename friendly keys such as configuration URLs and JWKs URIs - enable JQ-based claims expression matching when compiled from source with --with-jq=, e.g.: Require claims_expr '.aud == "ac_oic_client" and (.scope | index("profile") != null)' - normalize cache backend logging - bump to 2.3.0rc3 06/06/2017 - avoid cleaning our own state cookie twice when it is expired - bump to 2.3.0rc2 06/02/2017 - refactor remote user handling so it allows for postfixing with the issuer value after applying the regex - bump to 2.3.0rc1 05/31/2017 - add support for custom actions to take after authorization fails with OIDCUnAutzAction this enables stepup authentication scenarios when combined with the following: - add OIDCPathAuthRequestParams that is configurable on a per-path basis and use OIDCAuthRequestParams for the static per-provider value - add OIDCPathScope that is configurable on a per-path basis and concatenate with OIDCScope as static per-provider value - support 3rd-party-init-SSO with additional authentication request params when a single static provider has been configured - add support for an empty OIDCClaimPrefix; can be used with OIDCWhiteListedClaims to protect selected headers - bump to 2.3.0rc0 05/30/2017 - support sending Authorization Request as "request" object in addition to "request_uri"; thanks @suttod - support nested claim matching in Require directives; thanks @suttod - support explicitly setting the "kid" of the private key in OIDCPrivateKeyFiles; thanks @suttod 05/25/2017 - fix cache fallback so it happens (when enabled) only after failure 05/19/2017 - make OIDCStripCookies work on AuthType oauth20 paths; closes #273; thanks Michele Danieli - bump to 2.2.1rc6 05/18/2017 - fix parse function of OIDCRequestObject configuration option; thanks @suttod 05/17/2017 - avoid crash when the X-Forwarded-Proto header is not correctly set by a reverse proxy in front of mod_auth_openidc 05/14/2017 - support JWT verification against multiple keys with no provided kid by looping over the provided keys with cjose 0.5.0 - remove OIDC RP certification files; moved to separate repository 05/04/2017 - improve documentation for OIDCCryptoPassphrase; closes #268 04/30/2017 - fix wrong return value for cache_file_set in the file cache backend (OIDCCacheType file); thanks Ernani Joppert Pontes Martins - bump to 2.2.1rc5 04/29/2017 - correctly log success/failure in cache_file_set - avoid decoding a JSON object and logging an error when the input is NULL e.g. when claims have not been resolved because userinfo endpoint is not set 04/20/2017 - support relative RedirectURIs; closes #200; thanks @moschlar - don't assume that having OIDCCryptPassphrase set means we should validate the config for openid-connect since it can now also be used to encrypt (auth20) cache entries - bump to 2.2.1rc4 04/08/2017 - fix potential crash on prefork process exit when used with Redis cache backend (3x) - bump to 2.2.1rc3 04/06/2017 - change warn log about missing token binding ID to debug log 04/05/2017 - allow for high session inactivity timeout max value - improve error message in oidc_util_http_send when ap_pass_brigade fails and mention possible interference with mod_deflate - bump to 2.2.1rc0 03/30/2017 - merge feature branch back to master: - better support for Single Page Applications, see: https://github.com/zmartzone/mod_auth_openidc/wiki/Single-Page-Applications - add session info hook that is configurable through OIDCInfoHook - add "AuthType auth-openidc" option that allows both "oauth20" and "openid-connect" on the same path - add encryption for all cache entries instead of just session data through OIDCCacheEncrypt - add cookie SameSite flag/policy through OIDCCookieSameSite - return HTTP 200 on OPTIONS requests to (unauthenticated) "oauth20" paths - add fallback to a by-value session cookie if the primary session cache fails with OIDCSessionCacheFallbackToCookie - add support for black- and/or white-listing claims with OIDCBlackListedClaims and OIDCWhiteListedClaims - add prototype token binding support in conjunction with: https://github.com/zmartzone/mod_token_binding: - for state & session cookies, see: https://github.com/TokenBinding/Internet-Drafts - for ID tokens with OpenID Connect Token Bound Authentication support, see: http://openid.net/specs/openid-connect-token-bound-authentication-1_0.html - for Authorization Codes with OAuth 2.0 Token Binding for Authorization Codes, see: https://tools.ietf.org/html/draft-ietf-oauth-token-binding - refactoring: - refactor session state, proto state and headers into getters/setters functions - refactor PKCE support - fix removing session state from cache on logout - fix clearing chunked session cookies on logout; closes #246; thanks @Jharmuth - release 2.2.0 02/20/2017 - security fix: scrub headers for "AuthType oauth20" - release 2.1.6 02/15/2017 - improve logging of session max duration and session inactivity timeout - refactor so that the call to the refresh hook also resets the session inactivity timeout and passes tokens down 02/14/2017 - treat only "X-Requested-With: XMLHttpRequest" header as a non-browser client; closes #228 ; thanks @mguillem - improve error message on state timeout; closes #226; thanks @security4java 02/09/2017 - correctly parse "kid" in OIDCPublicKeyFiles and OIDCOAuthVerifyCertFiles; thanks Alessandro Papacci - bump to 2.1.6rc2 02/07/2017 - fix parsing of mandatory/optional attribute in OIDCOAuthTokenExpiryClaim; closes #225; thanks Alessandro Papacci - bump to 2.1.6rc1 02/06/2017 - improve logging around the availability of session management; closes #223 02/02/2017 - interpret OIDCUnAuthAction also when the maximum session duration has been exceeded; see #220 - bump to 2.1.6rc0 01/30/2017 - security fix: scrub headers when `OIDCUnAuthAction pass` is used for an unauthenticated user - release 2.1.5 01/29/2017 - fix error message about passing id_token with session type client-cookie; mentioned in #220 - bump to 2.1.5rc0 01/25/2017 - release 2.1.4 01/18/2017 - don't echo the query parameters on the error page when an invalid request is made to the Redirect URI; closes #212; thanks @LukasReschke 01/14/2017 - use dynamic memory buffer for writing HTTP call responses; solves curl/mpm-event interference; see #207 - bump to 2.1.4rc1 01/10/2017 - don't crash when data is POST-ed to the redirect URL, it has just 1 POST parameter and it is not "response_mode" 01/2/2017 - remove trailing linebreaks from input in test-cmd tool - bump copyright year to 2017 12/14/2016 - support Libre SSL, see #205, thanks @AliceWonderMiscreations - update OIDC logout support to Front-Channel Logout 1.0 draft 01: http://openid.net/specs/openid-connect-frontchannel-1_0.html - bump to 2.1.4rc0 12/13/2016 - release 2.1.3 12/12/2016 - don't rollover session id's and keep the same session cookie name for cache storage over session updates - bump to 2.1.3rc0 11/19/2016 - release 2.1.2 11/18/2016 - fix crash when searching for keys with a kid, there's no initial match and x5t values exist for the non-matching keys; closes #196 11/9/2016 - remove stale claims from session when refreshing them from the userinfo endpoint fails; addresses #194 - release 2.1.1 11/8/2016 - log readable error messages when memcache operations fail 11/6/2016 - fix memory leak when skipping jwks_uri keys with a non-matching "use" value 11/4/2016 - always restore id_token/claims on sub-requests so e.g. listing claims-protected subdirectories will work - remove obsolete functions for storing the session in the request state - bump to 2.1.1rc0 11/3/2016 - remove obsolete sessions from session cache; thanks @stevedave 11/1/2016 - release version 2.1.0 10/28/2016 - don't include encryption keys from the jwks_uri when verifying a JWT and no kid has been specified - fix memory leaks in composite claim handling 10/27/2016 - handle aggregated and distributed claims from the userinfo endpoint - only pick private_key_jwt token endpoint authentication if a private key is configured; closes #189 - bump to 2.0.1rc7 10/24/2016 - add OpenID Connect RP certification test script - handle non-integer exp/iat timestamps; closes #187; thanks @drdivano 10/21/2016 - bugfix: first truncate files before writing them - support refreshing provider metadata based on timestamp and OIDCProviderMetadataRefreshInterval 10/20/2016 - bugfix: correctly truncate encryption keys derived from client secret for algorithms that require a key size < 256 bits - add test/test-cmd tool - bugfix: return error on session cache failures; closes #185; thanks @solsson - bump to 2.0.1rc6 10/18/2016 - bugfix: JWTs with a header that doesn't specify a `kid` that would not validate when used with more than 1 key; closes #184; thanks @solsson - bump to 2.0.1rc5 10/13/2016 - urlencode provider URL cache key to fix file cache backend issue; closes #179, thanks @djahandarie 10/9/2016 - fix null pointer segfault in debug printout in oidc_util_read_form_encoded_params - fix OIDCOAuthAcceptTokenAs parsing flaw introduced in 2.0.0rc5 - bump to 2.0.1rc4 10/2/2016 - support presenting the access token to the userinfo endpoint in a POST parameter - bump to 2.0.1rc3 9/30/2016 - support WebFinger Discovery with URL-style user identifiers 9/28/2016 - fix memory leak in oidc_jwk_to_json - add "remove_at_cache" hook; addresses #177 - bump to 2.0.1rc2 9/27/2016 - add support for Request URI with signed and/or encrypted Request Objects - bump to 2.0.1rc1 9/22/2016 - refuse webfinger responses with an href value that is not on secure https - add userinfo JWT response verification and decryption 9/20/2016 - log the JWT header before optional decryption is applied 9/19/2016 - check that a sub claim returned from the userinfo endpoint matches the one in the id_token - fix issue in oidc_metadata_parse_url so that static default would not be honored - this only affected server-wide OIDCClientJwksUri usage in dynamic client registration - non-functional changes for OIDC RP certification: - explicitly log the client authentication method when calling the token endpoint - log the keys that are included for token verification - bump to 2.0.1rc0 9/9/2016 - fix overriding provider token endpoint auth with static config when not set in .conf file - don't add our own cookies to the incoming headers - allow stripping cookies from the Cookie header sent to the application/backend with OIDCStripCookies - release 2.0.0 9/5/2016 - encapsulate (sub-)directory config handling and fix merging so values can be set back to default values in subdirs - bump to 2.0.0rc5 9/2/2016 - fix JWK creation when no client secret is set e.g. in Implicit flows; closes #168; thanks @asc1 - bump to 2.0.0rc4 9/1/2016 - fix HTML decoding of OIDCPreservePost data; closes #165 - limit max POST data size to 1Mb - allow chunked data in POST handling; revise handler - change preserve POST JSON data format to urlencoded for performance reasons 8/31/2016 - allow setting the token endpoint authentication method in the .conf file (for dynamic client registration that sets the .client) 8/30/2016 - pass refresh token in header/environment variable with OIDCPassRefreshToken; thanks Amit Joshi - fix front-channel img-style logout with newer versions of PingFederate that don't send an Accept: image/png header 8/29/2016 - preserve POST data across authentication requests and discovery with OIDCPreservePost - bump to 2.0.0rc3 8/24/2016 - fix parsing of OIDCOAuthAcceptTokenAs to accept options following ":" - bump to 2.0.0rc2 8/5/2016 - delete the debian directory - rename OIDCOAuthTokenEndpointCert/Key to OIDCOAuthIntrospectionEndpointCert/Key - pre-release 2.0.0rc1 7/30/2016 - encrypt state/session JWT cookies and session JWT cache values for non-shm storages 7/29/2016 - use cjose - https://github.com/cisco/cjose (master) - for JOSE functions - use stricter input parsing functions for configuration values - bump to 2.0.0rc0 7/21/2016 - support TLS client authentication to token and introspection endpoints - bump to 1.9.0rc3 7/19/2016 - add support for chunked session cookies; closes #153; thanks @glatzert - bump to 1.9.0rc2 7/9/2016 - fix Elliptic Curve signature verification for corrupted input - support OpenSSL 1.1.x - bump to 1.9.0rc1 7/5/2016 - use AUTHZ_DENIED instead of HTTP_UNAUTHORIZED in oidc_authz_checker; closes #151; thanks @gwollman - use signed JWTs for state/session cookies - achieve smaller client-cookie sizes for regular cases; no id_token is stored in the session: - (optional) id_token_hint no longer available in session management calls (logout/prompt=none) with "OIDCSessionType client-cookie" - "OIDCPassIDTokenAs serialized" is not available with "OIDCSessionType client-cookie" - bump to 1.9.0rc0 6/27/2016 - use EVP_CIPHER_CTX_new to avoid compilation errors with OpenSSL 1.1.0 - release 1.8.10 6/22/2016 - don't use local port setting for current URL determination when X-Forwarded-Host has been set - bump to 1.8.10rc4 6/20/2016 - fix memory leak in OAuth access token introspection result caching (introduced only in 1.8.10rc0) - fix setting private_key_jwt or client_secret_jwt with OIDCProviderTokenEndpointAuth - bump to 1.8.10rc3 6/19/2016 - allow setting OIDCRemoteUserClaim with values obtained from the userinfo endpoint; thanks @steve-dave - fix OIDCUnAuthAction pass mode for Apache 2.4 and in case `Require claim` primitives used for 2.4 and 2.2; thanks @steve-dave - bump to 1.8.10rc2 6/15/2016 - add support for JWT based client authentication to the token endpoint (client_secret_jwt, private_key_jwt) - bump to 1.8.10rc1 6/9/2016 - add per-path configurable token introspection result cache expiry with OIDCOAuthTokenIntrospectionInterval - bump to 1.8.10rc0 6/5/2016 - release 1.8.9 5/9/2016 - support 410 option on OIDCUnAuthAction; closes #141 - bump to 1.8.9rc6 5/1/2016 - avoid segmentation fault on invalid OIDC configuration when OIDCRedirectURI is not set; fixes #138; thanks @brianwcook - bump to 1.8.9rc5 4/18/2016 - fix get_current_url (proxy) case where r->parsed_uri.path would be null 4/13/2016 - improve X-Forwarded-Host handling over Host in a) port detection and b) remove port from host value - bump to 1.8.9rc4 4/10/2016 - do not require OIDCClientSecret in configs; allows for Implicit grant without setting a dummy client secret; closes #130 - allow for public clients calling the token endpoint - bump to 1.8.9rc3 4/9/2016 - ensure that claims from id_token are available for authz also when OIDCPassIDTokenAs does not contain "claims"; closes #129 - bump to 1.8.9rc2 4/3/2016 - return WWW-Authenticate header and error messages on OAuth paths where access is not granted; closes #124; thanks @spinto - bump to 1.8.9rc1 4/1/2016 - apr_jwe_decrypt_content_aesgcm() null terminate string, #128, thanks @jdennis - bump to 1.8.9rc0 3/10/2016 - release 1.8.8 3/7/2016 - issue a warning if the "openid" scope is not requested 3/6/2016 - sanitize the OIDCAuthNHeader value before setting the header; thanks @rfk - bump to 1.8.8rc7 3/5/2016 - log exact version of OpenSSL and EC/GCM/Redis support - tidy up auth_openidc.conf docs - bump to 1.8.8rc6 2/26/2016 - add option to refresh claims from the userinfo endpoint using OIDCUserInfoRefreshInterval; see #119 - merge id_token claims in to the set of claims used for authorization for Apache >=2.4; see #120 - bump to 1.8.8rc5 2/23/2016 - make state cookie a session cookie and clean expired cookies on entry (merge of fix-firefox-cookie-storage) - fix HTML error template initialization in vhosts - bump to 1.8.8rc4 2/22/2016 - don't authenticate (redirect/state) when X-Requested-With header exists; as suggested in #113 - bump to 1.8.8rc3 2/18/2016 - pass plain state to the token endpoint on code flows: https://tools.ietf.org/html/draft-jones-oauth-mix-up-mitigation-01 - fix loose (prefix-only) matching of cookie names - allow passing OAuth bearer token as a cookie (OIDCOAuthAcceptTokenAs extension for PingAccess) - bump to 1.8.8rc2 2/11/2016 - include token_endpoint_auth_method in dynamic client registration request, set to selected method from provider 2/10/2016 - Elliptic Curve support now requires OpenSSL 1.0.1 detection - bump to 1.8.8rc1 1/14/2016 - add support for passing in OAuth bearer tokens as one or more of: header, post param or query param (OIDCOAuthAcceptTokenAs) - bump to 1.8.8rc0 1/8/2016 - release 1.8.7 1/7/2016 - update copyright year 12/17/2015 - enforce strict matching of issuer in Discovery document against the originally requested issuer - check iss/client_id if present in an authentication response - push a hash of state to the token endpoint on code flows - bump to 1.8.7rc4 12/9/2015 - improve debug logging around session management capabilities (i.e. enabled/disabled) - return 404 for op/rp iframes if session management is not enabled - bump to 1.8.7rc3 12/4/2015 - add support for RFC 7636 PKCE plain & S256 https://tools.ietf.org/html/rfc7636 - bump to 1.8.7rc2 12/3/2015 - fix crash when using a custom error template and the error description is NULL - fix crash when target_link_uri is not a valid URI or parts are empty - fix memory corruption when using custom html template across different server requests; closes #106 - bump to 1.8.7rc1 11/18/2015 - fix compiler warning on double sizeof call; close #103; thanks to @dcb314 - bump to 1.8.7rc0 10/26/2015 - add option to make session cookie persistent; closes #97 - release 1.8.6 10/19/2015 - add support for applying a custom HTML error template with OIDCHTMLErrorTemplate - bump to 1.8.6rc3 10/12/2015 - check the cookie domain that the session was created for against the configured domain - log a warning if the Set-Cookie value length is greater than 4093 bytes - include and prioritize the X-Forwarded-Host header in hostname determination - allow for missing Host header i.e. HTTP 1.0 - return DONE instead of HTTP_UNAUTHORIZED with Discovery page (prevent double HTML in HTTP 1.0) - use apr_strnatcmp instead of strcmp in util.c and mod_auth_openidc.c - bump to 1.8.6rc2 10/9/2015 - support subdomain cookies in OIDCCookieDomain checks; PR #96, thanks @pfiled - bump to 1.8.6rc1 10/6/2015 - add key identifier ("kid") option to `OIDCOAuthVerifySharedKeys`, `OIDCOAuthVerifyCertFiles` and `OIDCPublicKeyFiles` configs - bump to 1.8.6rc0 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