/*
 * return JWKs for the specified issuer
 */
apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *cfg,
		const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks, apr_byte_t *refresh) {

	oidc_debug(r, "enter, jwks_uri=%s, refresh=%d", jwks_uri->url, *refresh);

	/* see if we need to do a forced refresh */
	if (*refresh == TRUE) {
		oidc_debug(r, "doing a forced refresh of the JWKs from URI \"%s\"",
				jwks_uri->url);
		if (oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri,
				j_jwks) == TRUE)
			return TRUE;
		// else: fallback on any cached JWKs
	}

	/* see if the JWKs is cached */
	const char *value = NULL;
	cfg->cache->get(r, OIDC_CACHE_SECTION_JWKS,
			oidc_metadata_jwks_cache_key(r, jwks_uri->url), &value);

	if (value == NULL) {
		/* it is non-existing or expired: do a forced refresh */
		*refresh = TRUE;
		return oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, j_jwks);
	}

	/* decode and see if it is not an error response somehow */
	if (oidc_util_decode_json_and_check_error(r, value, j_jwks) == FALSE)
		return FALSE;

	return TRUE;
}
示例#2
0
/*
 * get the authorization header that should contain a bearer token
 */
static apr_byte_t oidc_oauth_get_bearer_token(request_rec *r,
		const char **access_token) {

	/* get the authorization header */
	const char *auth_line;
	auth_line = apr_table_get(r->headers_in, "Authorization");
	if (!auth_line) {
		oidc_debug(r, "no authorization header found");
		return FALSE;
	}

	/* look for the Bearer keyword */
	if (apr_strnatcasecmp(ap_getword(r->pool, &auth_line, ' '), "Bearer")) {
		oidc_error(r, "client used unsupported authentication scheme: %s",
				r->uri);
		return FALSE;
	}

	/* skip any spaces after the Bearer keyword */
	while (apr_isspace(*auth_line)) {
		auth_line++;
	}

	/* copy the result in to the access_token */
	*access_token = apr_pstrdup(r->pool, auth_line);

	/* log some stuff */
	oidc_debug(r, "bearer token: %s", *access_token);

	return TRUE;
}
示例#3
0
/*
 * Apache >=2.4 authorization routine: match the claims from the authenticated user against the Require primitive
 */
authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims, const char *require_args) {

	int count_oauth_claims = 0;
	const char *t, *w;

	/* needed for anonymous authentication */
	if (r->user == NULL) return AUTHZ_DENIED_NO_USER;

	/* if no claims, impossible to satisfy */
	if (!claims) return AUTHZ_DENIED;

	/* loop over the Required specifications */
	t = require_args;
	while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {

		count_oauth_claims++;

		oidc_debug(r, "evaluating claim specification: %s", w);

		/* see if we can match any of out input claims against this Require'd value */
		if (oidc_authz_match_claim(r, w, claims) == TRUE) {

			oidc_debug(r, "require claim '%s' matched", w);
			return AUTHZ_GRANTED;
		}
	}

	/* if there wasn't anything after the Require claims directive... */
	if (count_oauth_claims == 0) {
		oidc_warn(r,
				"'require claim' missing specification(s) in configuration, denying");
	}

	return AUTHZ_DENIED;
}
示例#4
0
/*
 * validate a JWT access token (locally)
 *
 * TODO: document that we're reusing the following settings from the OIDC config section:
 *       - JWKs URI refresh interval
 *       - encryption key material (OIDCPrivateKeyFiles)
 *       - iat slack (OIDCIDTokenIatSlack)
 *
 * OIDCOAuthRemoteUserClaim client_id
 * # 32x 61 hex
 * OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 */
static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r,
		oidc_cfg *c, const char *access_token, json_t **token, char **response) {

	apr_jwt_error_t err;
	apr_jwt_t *jwt = NULL;
	if (apr_jwt_parse(r->pool, access_token, &jwt,
			oidc_util_merge_symmetric_key(r->pool, c->private_keys,
					c->oauth.client_secret, NULL), &err) == FALSE) {
		oidc_error(r, "could not parse JWT from access_token: %s",
				apr_jwt_e2s(r->pool, err));
		return FALSE;
	}

	oidc_debug(r, "successfully parsed JWT with header: %s", jwt->header.value.str);

	/* validate the access token JWT, validating optional exp + iat */
	if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE,
			c->provider.idtoken_iat_slack) == FALSE) {
		apr_jwt_destroy(jwt);
		return FALSE;
	}

	oidc_debug(r,
			"verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s",
			c->oauth.verify_public_keys ?
					apr_hash_count(c->oauth.verify_public_keys) : 0,
					c->oauth.verify_shared_keys ?
							apr_hash_count(c->oauth.verify_shared_keys) : 0,
							c->oauth.verify_jwks_uri);

	oidc_jwks_uri_t jwks_uri = { c->oauth.verify_jwks_uri,
			c->provider.jwks_refresh_interval, c->oauth.ssl_validate_server };
	if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri,
			oidc_util_merge_key_sets(r->pool, c->oauth.verify_public_keys,
					c->oauth.verify_shared_keys)) == FALSE) {
		oidc_error(r,
				"JWT access token signature could not be validated, aborting");
		apr_jwt_destroy(jwt);
		return FALSE;
	}

	oidc_debug(r, "successfully verified JWT access token: %s",
			jwt->payload.value.str);

	*token = jwt->payload.value.json;
	*response = jwt->payload.value.str;

	return TRUE;
}
示例#5
0
/*
 * check to see if dynamically registered JSON client metadata is valid and has not expired
 */
static apr_byte_t oidc_metadata_client_is_valid(request_rec *r,
		json_t *j_client, const char *issuer) {

	/* get a handle to the client_id we need to use for this provider */
	json_t *j_client_id = json_object_get(j_client, "client_id");
	if ((j_client_id == NULL) || (!json_is_string(j_client_id))) {
		oidc_error(r,
				"client (%s) JSON metadata did not contain a \"client_id\" string",
				issuer);
		return FALSE;
	}

	/* get a handle to the client_secret we need to use for this provider */
	json_t *j_client_secret = json_object_get(j_client, "client_secret");
	if ((j_client_secret == NULL) || (!json_is_string(j_client_secret))) {
		oidc_warn(r,
				"client (%s) JSON metadata did not contain a \"client_secret\" string",
				issuer);
		//return FALSE;
	}

	/* the expiry timestamp from the JSON object */
	json_t *expires_at = json_object_get(j_client, "client_secret_expires_at");
	if ((expires_at == NULL) || (!json_is_integer(expires_at))) {
		oidc_debug(r,
				"client (%s) metadata did not contain a \"client_secret_expires_at\" setting",
				issuer);
		/* assume that it never expires */
		return TRUE;
	}

	/* see if it is unrestricted */
	if (json_integer_value(expires_at) == 0) {
		oidc_debug(r,
				"client (%s) metadata never expires (client_secret_expires_at=0)",
				issuer);
		return TRUE;
	}

	/* check if the value >= now */
	if (apr_time_sec(apr_time_now()) > json_integer_value(expires_at)) {
		oidc_warn(r, "client (%s) secret expired", issuer);
		return FALSE;
	}

	oidc_debug(r, "client (%s) metadata is valid", issuer);

	return TRUE;
}
示例#6
0
/*
 * load the session from the request context, create a new one if no luck
 */
static apr_status_t oidc_session_load_22(request_rec *r, session_rec **zz) {

	oidc_cfg *c = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);

	/* first see if this is a sub-request and it was set already in the main request */
	if (((*zz) = (session_rec *) oidc_request_state_get(r, "session")) != NULL) {
		oidc_debug(r, "loading session from request state");
		return APR_SUCCESS;
	}

	/* allocate space for the session object and fill it */
	session_rec *z = (*zz = apr_pcalloc(r->pool, sizeof(session_rec)));
	z->pool = r->pool;

	/* get a new uuid for this session */
	z->uuid = (apr_uuid_t *) apr_pcalloc(z->pool, sizeof(apr_uuid_t));
	apr_uuid_get(z->uuid);

	z->remote_user = NULL;
	z->encoded = NULL;
	z->entries = apr_table_make(z->pool, 10);

	apr_status_t rc = APR_SUCCESS;
	if (c->session_type == OIDC_SESSION_TYPE_22_SERVER_CACHE) {
		/* load the session from the cache */
		rc = oidc_session_load_cache(r, z);
	} else if (c->session_type == OIDC_SESSION_TYPE_22_CLIENT_COOKIE) {
		/* load the session from a self-contained cookie */
		rc = oidc_session_load_cookie(r, z);
	} else {
		oidc_error(r, "oidc_session_load_22: unknown session type: %d",
				c->session_type);
		rc = APR_EGENERAL;
	}

	/* see if it worked out */
	if (rc != APR_SUCCESS)
		return rc;

	/* yup, now decode the info */
	if (oidc_session_identity_decode(r, z) != APR_SUCCESS)
		return APR_EGENERAL;

	/* check whether it has expired */
	if (apr_time_now() > z->expiry) {

		oidc_warn(r, "session restored from cache has expired");
		apr_table_clear(z->entries);
		z->expiry = 0;
		z->encoded = NULL;

		return APR_EGENERAL;
	}

	/* store this session in the request context, so it is available to sub-requests */
	oidc_request_state_set(r, "session", (const char *) z);

	return APR_SUCCESS;
}
示例#7
0
/*
 * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
 */
static apr_byte_t oidc_oauth_set_remote_user(request_rec *r, oidc_cfg *c,
		json_t *token) {

	/* get the configured claim name to populate REMOTE_USER with (defaults to "Username") */
	char *claim_name = apr_pstrdup(r->pool, c->oauth.remote_user_claim.claim_name);

	/* get the claim value from the resolved token JSON response to use as the REMOTE_USER key */
	json_t *username = json_object_get(token, claim_name);
	if ((username == NULL) || (!json_is_string(username))) {
		oidc_warn(r, "response JSON object did not contain a \"%s\" string",
				claim_name);
		return FALSE;
	}

	r->user = apr_pstrdup(r->pool, json_string_value(username));

	if (c->oauth.remote_user_claim.reg_exp != NULL) {

		char *error_str = NULL;
		if (oidc_util_regexp_first_match(r->pool, r->user, c->oauth.remote_user_claim.reg_exp, &r->user, &error_str) == FALSE) {
			oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str);
			r->user = NULL;
			return FALSE;
		}
	}

	oidc_debug(r, "set REMOTE_USER to claim %s=%s", claim_name,
			json_string_value(username));

	return TRUE;
}
示例#8
0
/*
 * 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) {

	/* hash the provided access_token */
	char *calc = NULL;
	unsigned int hash_len = 0;
	apr_jws_hash_string(r->pool, alg, value, &calc, &hash_len);

	/* calculate the base64url-encoded value of the hash */
	char *encoded = NULL;
	oidc_base64url_encode(r, &encoded, calc, apr_jws_hash_length(alg) / 2, 1);

	/* compare the calculated hash against the provided hash */
	if ((apr_strnatcmp(encoded, hash) != 0)) {
		oidc_error(r,
				"provided \"%s\" hash value (%s) does not match the calculated value (%s)",
				type, hash, encoded);
		return FALSE;
	}

	oidc_debug(r,
			"successfully validated the provided \"%s\" hash value (%s) against the calculated value (%s)",
			type, hash, encoded);

	return TRUE;
}
示例#9
0
/*
 * set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
 */
static apr_byte_t oidc_proto_set_remote_user(request_rec *r, oidc_cfg *c,
		oidc_provider_t *provider, apr_jwt_t *jwt, char **user) {

	char *issuer = provider->issuer;
	char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim);
	int n = strlen(claim_name);
	int post_fix_with_issuer = (claim_name[n - 1] == '@');
	if (post_fix_with_issuer) {
		claim_name[n - 1] = '\0';
		issuer =
				(strstr(issuer, "https://") == NULL) ?
						apr_pstrdup(r->pool, issuer) :
						apr_pstrdup(r->pool, issuer + strlen("https://"));
	}

	/* extract the username claim (default: "sub") from the id_token payload */
	char *username = NULL;
	apr_jwt_get_string(r->pool, &jwt->payload.value, claim_name, &username);

	if (username == NULL) {
		oidc_error(r,
				"OIDCRemoteUserClaim is set to \"%s\", but the id_token JSON payload did not contain a \"%s\" string",
				c->remote_user_claim, claim_name);
		return FALSE;
	}

	/* set the unique username in the session (will propagate to r->user/REMOTE_USER) */
	*user = post_fix_with_issuer ?
			apr_psprintf(r->pool, "%s@%s", username, issuer) :
			apr_pstrdup(r->pool, username);

	oidc_debug(r, "set remote_user to \"%s\"", *user);

	return TRUE;
}
示例#10
0
/*
 * connect to Redis server
 */
static redisContext * oidc_cache_redis_connect(request_rec *r,
		oidc_cache_cfg_redis_t *context) {

	/* see if we already have a connection by looking it up in the process context */
	redisContext *ctx = NULL;
	apr_pool_userdata_get((void **) &ctx, OIDC_CACHE_REDIS_CONTEXT,
			r->server->process->pool);

	if (ctx == NULL) {

		/* no connection, connect to the configured Redis server */
		ctx = redisConnect(context->host_str, context->port);

		/* check for errors */
		if ((ctx == NULL) || (ctx->err != 0)) {
			oidc_error(r, "failed to connect to Redis server (%s:%d): '%s'",
					context->host_str, context->port, ctx->errstr);
			return NULL;
		}

		/* store the connection in the process context */
		apr_pool_userdata_set(ctx, OIDC_CACHE_REDIS_CONTEXT,
				(apr_status_t (*)(void *)) redisFree, r->server->process->pool);

		/* log the connection */
		oidc_debug(r, "successfully connected to Redis server (%s:%d)",
				context->host_str, context->port);
	}

	return ctx;
}
示例#11
0
int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c) {

	oidc_debug(r, "enter");

	const char *java_script =
			"    <script type=\"text/javascript\">\n"
					"      function postOnLoad() {\n"
					"        encoded = location.hash.substring(1).split('&');\n"
					"        for (i = 0; i < encoded.length; i++) {\n"
					"          encoded[i].replace(/\\+/g, ' ');\n"
					"          var n = encoded[i].indexOf('=');\n"
					"          var input = document.createElement('input');\n"
					"          input.type = 'hidden';\n"
					"          input.name = decodeURIComponent(encoded[i].substring(0, n));\n"
					"          input.value = decodeURIComponent(encoded[i].substring(n+1));\n"
					"          document.forms[0].appendChild(input);\n"
					"        }\n"
					"        document.forms[0].action = window.location.href.substr(0, window.location.href.indexOf('#'));\n"
					"        document.forms[0].submit();\n"
					"      }\n"
					"    </script>\n";

	const char *html_body =
			"    <p>Submitting...</p>\n"
					"    <form method=\"post\" action=\"\">\n"
					"      <p>\n"
					"        <input type=\"hidden\" name=\"response_mode\" value=\"fragment\">\n"
					"      </p>\n"
					"    </form>\n";

	return oidc_util_html_send(r, "Submitting...", java_script, "postOnLoad",
			html_body, DONE);
}
示例#12
0
/*
 * 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;
}
示例#13
0
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);
}
示例#14
0
apr_status_t oidc_session_save(request_rec *r, session_rec *z) {
	oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user);
	char key[APR_UUID_FORMATTED_LENGTH + 1];
	apr_uuid_format((char *) &key, z->uuid);
	oidc_debug(r, "%s", key);
	oidc_session_set(r, z, OIDC_SESSION_UUID_KEY, key);
	return ap_session_save_fn(r, z);
}
示例#15
0
/*
 * store a name/value pair in memcache
 */
static apr_byte_t oidc_cache_memcache_set(request_rec *r, const char *section,
		const char *key, const char *value, apr_time_t expiry) {

	oidc_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);

	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);
	oidc_cache_cfg_memcache_t *context =
			(oidc_cache_cfg_memcache_t *) cfg->cache_cfg;

	apr_status_t rv = APR_SUCCESS;

	/* see if we should be clearing this entry */
	if (value == NULL) {

		rv = apr_memcache_delete(context->cache_memcache,
				oidc_cache_memcache_get_key(r->pool, section, key), 0);

		if (rv == APR_NOTFOUND) {
			oidc_debug(r, "apr_memcache_delete: key %s not found in cache",
					oidc_cache_memcache_get_key(r->pool, section, key));
		} else if (rv != APR_SUCCESS) {
			// TODO: error strings ?
			oidc_error(r,
					"apr_memcache_delete returned an error; perhaps your memcache server is not available?");
		}

	} else {

		/* calculate the timeout from now */
		apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now());

		/* store it */
		rv = apr_memcache_set(context->cache_memcache,
				oidc_cache_memcache_get_key(r->pool, section, key),
				(char *) value, strlen(value), timeout, 0);

		// TODO: error strings ?
		if (rv != APR_SUCCESS) {
			oidc_error(r, "apr_memcache_set returned an error; perhaps your memcache server is not available?");
		}
	}

	return (rv == APR_SUCCESS);
}
示例#16
0
apr_status_t oidc_session_load(request_rec *r, session_rec **zz) {
	apr_status_t rc = ap_session_load_fn(r, zz);
	(*zz)->remote_user = apr_table_get((*zz)->entries,
			OIDC_SESSION_REMOTE_USER_KEY);
	const char *uuid = apr_table_get((*zz)->entries, OIDC_SESSION_UUID_KEY);
	oidc_debug(r, "%s", uuid ? uuid : "<null>");
	if (uuid != NULL)
		apr_uuid_parse((*zz)->uuid, uuid);
	return rc;
}
示例#17
0
/*
 * write JSON metadata to a file
 */
static apr_byte_t oidc_metadata_file_write(request_rec *r, const char *path,
		const char *data) {

	// TODO: completely erase the contents of the file if it already exists....

	apr_file_t *fd = NULL;
	apr_status_t rc = APR_SUCCESS;
	apr_size_t bytes_written = 0;
	char s_err[128];

	/* try to open the metadata file for writing, creating it if it does not exist */
	if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE),
			APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
		oidc_error(r, "file \"%s\" could not be opened (%s)", path,
				apr_strerror(rc, s_err, sizeof(s_err)));
		return FALSE;
	}

	/* lock the file and move the write pointer to the start of it */
	apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
	apr_off_t begin = 0;
	apr_file_seek(fd, APR_SET, &begin);

	/* calculate the length of the data, which is a string length */
	apr_size_t len = strlen(data);

	/* (blocking) write the number of bytes in the buffer */
	rc = apr_file_write_full(fd, data, len, &bytes_written);

	/* check for a system error */
	if (rc != APR_SUCCESS) {
		oidc_error(r, "could not write to: \"%s\" (%s)", path,
				apr_strerror(rc, s_err, sizeof(s_err)));
		return FALSE;
	}

	/* check that all bytes from the header were written */
	if (bytes_written != len) {
		oidc_error(r,
				"could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")",
				path, bytes_written, len);
		return FALSE;
	}

	/* unlock and close the written file */
	apr_file_unlock(fd);
	apr_file_close(fd);

	oidc_debug(r, "file \"%s\" written; number of bytes (%" APR_SIZE_T_FMT ")",
			path, len);

	return TRUE;
}
示例#18
0
/*
 * get a value from the shared memory cache
 */
static apr_byte_t oidc_cache_shm_get(request_rec *r, const char *section,
		const char *key, const char **value) {

	oidc_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);

	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);
	oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg;

	int i;
	const char *section_key = oidc_cache_shm_get_key(r->pool, section, key);

	*value = NULL;

	/* grab the global lock */
	if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
		return FALSE;

	/* get the pointer to the start of the shared memory block */
	oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm);

	/* loop over the block, looking for the key */
	for (i = 0; i < cfg->cache_shm_size_max; i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) {
		const char *tablekey = t->section_key;

		if ( (tablekey != NULL) && (apr_strnatcmp(tablekey, section_key) == 0) ) {

			/* found a match, check if it has expired */
			if (t->expires > apr_time_now()) {

				/* update access timestamp */
				t->access = apr_time_now();
				*value = t->value;

			} else {

				/* clear the expired entry */
				t->section_key[0] = '\0';
				t->access = 0;

			}

			/* we safely can break now since we would not have found an expired match twice */
			break;
		}
	}

	/* release the global lock */
	oidc_cache_mutex_unlock(r, context->mutex);

	return (*value == NULL) ? FALSE : TRUE;
}
示例#19
0
/*
 * see if a the Require value matches with a set of provided claims
 */
static apr_byte_t oidc_authz_match_claim(request_rec *r,
		const char * const attr_spec, const json_t * const claims) {

	const char *key;
	json_t *val;

	/* if we don't have any claims, they can never match any Require claim primitive */
	if (claims == NULL)
		return FALSE;

	/* loop over all of the user claims */
	void *iter = json_object_iter((json_t*) claims);
	while (iter) {

		key = json_object_iter_key(iter);
		val = json_object_iter_value(iter);

		oidc_debug(r, "evaluating key \"%s\"", (const char * ) key);

		const char *attr_c = (const char *) key;
		const char *spec_c = attr_spec;

		/* walk both strings until we get to the end of either or we find a differing character */
		while ((*attr_c) && (*spec_c) && (*attr_c) == (*spec_c)) {
			attr_c++;
			spec_c++;
		}

		/* The match is a success if we walked the whole claim name and the attr_spec is at a colon. */
		if (!(*attr_c) && (*spec_c) == ':') {

			/* skip the colon */
			spec_c++;

			if (oidc_authz_match_value(r, spec_c, val, key) == TRUE)
				return TRUE;

			/* a tilde denotes a string PCRE match */
		} else if (!(*attr_c) && (*spec_c) == '~') {

			/* skip the tilde */
			spec_c++;

			if (oidc_authz_match_expression(r, spec_c, val) == TRUE)
				return TRUE;
		}

		iter = json_object_iter_next((json_t *) claims, iter);
	}

	return FALSE;
}
示例#20
0
/*
 * store a name/value pair in Redis
 */
static apr_byte_t oidc_cache_redis_set(request_rec *r, const char *section,
		const char *key, const char *value, apr_time_t expiry) {

	oidc_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);

	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);
	oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
	redisReply *reply = NULL;

	/* grab the global lock */
	if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
		return FALSE;

	/* see if we should be clearing this entry */
	if (value == NULL) {

		/* delete it */
		reply = oidc_cache_redis_command(r, context, "DEL %s",
				oidc_cache_redis_get_key(r->pool, section, key));
		if (reply == NULL) {
			oidc_cache_mutex_unlock(r, context->mutex);
			return FALSE;
		}

		freeReplyObject(reply);

	} else {

		/* calculate the timeout from now */
		apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now());

		/* store it */
		reply = oidc_cache_redis_command(r, context, "SETEX %s %d %s",
				oidc_cache_redis_get_key(r->pool, section, key), timeout,
				value);
		if (reply == NULL) {
			oidc_cache_mutex_unlock(r, context->mutex);
			return FALSE;
		}

		freeReplyObject(reply);

	}

	/* release the global lock */
	oidc_cache_mutex_unlock(r, context->mutex);

	return TRUE;
}
示例#21
0
/*
 * get a name/value pair from memcache
 */
static apr_byte_t oidc_cache_memcache_get(request_rec *r, const char *section,
		const char *key, const char **value) {

	oidc_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);

	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);
	oidc_cache_cfg_memcache_t *context =
			(oidc_cache_cfg_memcache_t *) cfg->cache_cfg;

	apr_size_t len = 0;

	/* get it */
	apr_status_t rv = apr_memcache_getp(context->cache_memcache, r->pool,
			oidc_cache_memcache_get_key(r->pool, section, key), (char **) value,
			&len, NULL);

	if (rv == APR_NOTFOUND) {
		oidc_debug(r, "apr_memcache_getp: key %s not found in cache",
				oidc_cache_memcache_get_key(r->pool, section, key));
		return FALSE;
	} else if (rv != APR_SUCCESS) {
		// TODO: error strings ?
		oidc_error(r, "apr_memcache_getp returned an error; perhaps your memcache server is not available?");
		return FALSE;
	}

	/* do sanity checking on the string value */
	if ((*value) && (strlen(*value) != len)) {
		oidc_error(r,
				"apr_memcache_getp returned less bytes than expected: strlen(value) [%zu] != len [%" APR_SIZE_T_FMT "]",
				strlen(*value), len);
		return FALSE;
	}

	return TRUE;
}
示例#22
0
/*
 * 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;
}
示例#23
0
/*
 * get a list of configured OIDC providers based on the entries in the provider metadata directory
 */
apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg,
		apr_array_header_t **list) {
	apr_status_t rc;
	apr_dir_t *dir;
	apr_finfo_t fi;
	char s_err[128];

	oidc_debug(r, "enter");

	/* open the metadata directory */
	if ((rc = apr_dir_open(&dir, cfg->metadata_dir, r->pool)) != APR_SUCCESS) {
		oidc_error(r, "error opening metadata directory '%s' (%s)",
				cfg->metadata_dir, apr_strerror(rc, s_err, sizeof(s_err)));
		return FALSE;
	}

	/* allocate some space in the array that will hold the list of providers */
	*list = apr_array_make(r->pool, 5, sizeof(sizeof(const char*)));
	/* BTW: we could estimate the number in the array based on # directory entries... */

	/* loop over the entries in the provider metadata directory */
	while (apr_dir_read(&fi, APR_FINFO_NAME, dir) == APR_SUCCESS) {

		/* skip "." and ".." entries */
		if (fi.name[0] == '.')
			continue;
		/* skip other non-provider entries */
		char *ext = strrchr(fi.name, '.');
		if ((ext == NULL)
				|| (strcmp(++ext, OIDC_METADATA_SUFFIX_PROVIDER) != 0))
			continue;

		/* get the issuer from the filename */
		const char *issuer = oidc_metadata_filename_to_issuer(r, fi.name);

		/* get the provider and client metadata, do all checks and registration if possible */
		oidc_provider_t *provider = NULL;
		if (oidc_metadata_get(r, cfg, issuer, &provider) == 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;
}
示例#24
0
/*
 * get a name/value pair from Redis
 */
static apr_byte_t oidc_cache_redis_get(request_rec *r, const char *section,
		const char *key, const char **value) {

	oidc_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);

	oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
			&auth_openidc_module);
	oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
	redisReply *reply = NULL;

	/* grab the global lock */
	if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
		return FALSE;

	/* get */
	reply = oidc_cache_redis_command(r, context, "GET %s",
			oidc_cache_redis_get_key(r->pool, section, key));
	if (reply == NULL) {
		oidc_cache_mutex_unlock(r, context->mutex);
		return FALSE;
	}

	/* check that we got a string back */
	if (reply->type != REDIS_REPLY_STRING) {
		freeReplyObject(reply);
		/* this is a normal cache miss, so we'll return OK */
		oidc_cache_mutex_unlock(r, context->mutex);
		return TRUE;
	}

	/* do a sanity check on the returned value */
	if (reply->len != strlen(reply->str)) {
		oidc_error(r, "redisCommand reply->len != strlen(reply->str): '%s'",
				reply->str);
		freeReplyObject(reply);
		oidc_cache_mutex_unlock(r, context->mutex);
		return FALSE;
	}

	/* copy it in to the request memory pool */
	*value = apr_pstrdup(r->pool, reply->str);
	freeReplyObject(reply);

	/* release the global lock */
	oidc_cache_mutex_unlock(r, context->mutex);

	return TRUE;
}
示例#25
0
/*
 * 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
 */
static apr_byte_t oidc_proto_validate_nonce(request_rec *r, oidc_cfg *cfg,
		oidc_provider_t *provider, const char *nonce, apr_jwt_t *jwt) {

	/* see if we have this nonce cached already */
	const char *replay = NULL;
	cfg->cache->get(r, OIDC_CACHE_SECTION_NONCE, nonce, &replay);
	if (replay != NULL) {
		oidc_error(r,
				"the nonce value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
				nonce);
		return FALSE;
	}

	/* get the "nonce" value in the id_token payload */
	char *j_nonce = NULL;
	apr_jwt_get_string(r->pool, &jwt->payload.value, "nonce", &j_nonce);

	if (j_nonce == NULL) {
		oidc_error(r,
				"id_token JSON payload did not contain a \"nonce\" string");
		return FALSE;
	}

	/* see if the nonce in the id_token matches the one that we sent in the authorization request */
	if (apr_strnatcmp(nonce, j_nonce) != 0) {
		oidc_error(r,
				"the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)",
				j_nonce, nonce);
		return FALSE;
	}

	/*
	 * nonce cache duration (replay prevention window) is the 2x the configured
	 * slack on the timestamp (+-) for token issuance plus 10 seconds for safety
	 */
	apr_time_t nonce_cache_duration = apr_time_from_sec(
			provider->idtoken_iat_slack * 2 + 10);

	/* store it in the cache for the calculated duration */
	cfg->cache->set(r, OIDC_CACHE_SECTION_NONCE, nonce, nonce,
			apr_time_now() + nonce_cache_duration);

	oidc_debug(r,
			"nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds",
			nonce, apr_time_sec(nonce_cache_duration));

	return TRUE;
}
示例#26
0
/*
 * get claims from the OP UserInfo endpoint using the provided access_token
 */
apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg,
		oidc_provider_t *provider, const char *access_token,
		const char **response, json_t **claims) {

	oidc_debug(r, "enter, endpoint=%s, access_token=%s",
			provider->userinfo_endpoint_url, access_token);

	/* get the JSON response */
	if (oidc_util_http_get(r, provider->userinfo_endpoint_url,
			NULL, NULL, access_token, provider->ssl_validate_server, response,
			cfg->http_timeout_long, cfg->outgoing_proxy) == FALSE)
		return FALSE;

	/* decode and check for an "error" response */
	return oidc_util_decode_json_and_check_error(r, *response, claims);
}
示例#27
0
/*
 * refreshes the access_token/id_token /refresh_token received from the OP using the refresh_token
 */
apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg,
		oidc_provider_t *provider, const char *rtoken, char **id_token,
		char **access_token, char **token_type, int *expires_in,
		char **refresh_token) {

	oidc_debug(r, "enter");

	/* assemble the parameters for a call to the token endpoint */
	apr_table_t *params = apr_table_make(r->pool, 5);
	apr_table_addn(params, "grant_type", "refresh_token");
	apr_table_addn(params, "refresh_token", rtoken);
	apr_table_addn(params, "scope", provider->scope);

	return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token,
			access_token, token_type, expires_in, refresh_token);
}
示例#28
0
/*
 * resolves the code received from the OP in to an id_token, access_token and refresh_token
 */
apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg *cfg,
		oidc_provider_t *provider, const char *code, char **id_token,
		char **access_token, char **token_type, int *expires_in,
		char **refresh_token) {

	oidc_debug(r, "enter");

	/* assemble the parameters for a call to the token endpoint */
	apr_table_t *params = apr_table_make(r->pool, 5);
	apr_table_addn(params, "grant_type", "authorization_code");
	apr_table_addn(params, "code", code);
	apr_table_addn(params, "redirect_uri", cfg->redirect_uri);

	return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token,
			access_token, token_type, expires_in, refresh_token);
}
示例#29
0
/*
 * 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;
}
示例#30
0
/*
 * check to see if JSON provider metadata is valid
 */
static apr_byte_t oidc_metadata_provider_is_valid(request_rec *r,
		json_t *j_provider, const char *issuer) {

	/* get the "issuer" from the provider metadata and double-check that it matches what we looked for */
	json_t *j_issuer = json_object_get(j_provider, "issuer");
	if ((j_issuer == NULL) || (!json_is_string(j_issuer))) {
		oidc_error(r,
				"provider (%s) JSON metadata did not contain an \"issuer\" string",
				issuer);
		return FALSE;
	}

	/* check that the issuer matches */
	if (issuer != NULL) {
		if (oidc_util_issuer_match(issuer, json_string_value(j_issuer)) == FALSE) {
			oidc_warn(r,
					"requested issuer (%s) does not match the \"issuer\" value in the provider metadata file: %s",
					issuer, json_string_value(j_issuer));
			//return FALSE;
		}
	}

	/* verify that the provider supports the a flow that we implement */
	json_t *j_response_types_supported = json_object_get(j_provider,
			"response_types_supported");
	if ((j_response_types_supported != NULL)
			&& (json_is_array(j_response_types_supported))) {
		int i = 0;
		for (i = 0; i < json_array_size(j_response_types_supported); i++) {
			json_t *elem = json_array_get(j_response_types_supported, i);
			if (!json_is_string(elem)) {
				oidc_error(r,
						"unhandled in-array JSON non-string object type [%d]",
						elem->type);
				continue;
			}
			if (oidc_proto_flow_is_supported(r->pool, json_string_value(elem)))
				break;
		}
		if (i == json_array_size(j_response_types_supported)) {
			oidc_warn(r,
					"could not find a supported response type in provider metadata (%s) for entry \"response_types_supported\"; assuming that \"code\" flow is supported...",
					issuer);
			//return FALSE;
		}
	} else {
		oidc_warn(r,
				"provider (%s) JSON metadata did not contain a \"response_types_supported\" array; assuming that \"code\" flow is supported...",
				issuer);
		// TODO: hey, this is required-by-spec stuff right?
	}

	/* verify that the provider supports a response_mode that we implement */
	json_t *response_modes_supported = json_object_get(j_provider,
			"response_modes_supported");
	if ((response_modes_supported != NULL)
			&& (json_is_array(response_modes_supported))) {
		int i = 0;
		for (i = 0; i < json_array_size(response_modes_supported); i++) {
			json_t *elem = json_array_get(response_modes_supported, i);
			if (!json_is_string(elem)) {
				oidc_error(r,
						"unhandled in-array JSON non-string object type [%d]",
						elem->type);
				continue;
			}
			if ((apr_strnatcmp(json_string_value(elem), "fragment") == 0)
					|| (apr_strnatcmp(json_string_value(elem), "query") == 0)
					|| (apr_strnatcmp(json_string_value(elem), "form_post") == 0))
				break;
		}
		if (i == json_array_size(response_modes_supported)) {
			oidc_warn(r,
					"could not find a supported response mode in provider metadata (%s) for entry \"response_modes_supported\"",
					issuer);
			return FALSE;
		}
	} else {
		oidc_debug(r,
				"provider (%s) JSON metadata did not contain a \"response_modes_supported\" array; assuming that \"fragment\" and \"query\" are supported",
				issuer);
	}

	/* check the required authorization endpoint */
	if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
			"authorization_endpoint", TRUE) == FALSE)
		return FALSE;

	/* check the optional token endpoint */
	if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
			"token_endpoint", FALSE) == FALSE)
		return FALSE;

	/* check the optional user info endpoint */
	if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
			"userinfo_endpoint", FALSE) == FALSE)
		return FALSE;

	/* check the optional JWKs URI */
	if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
			"jwks_uri", FALSE) == FALSE)
		return FALSE;

	/* find out what type of authentication the token endpoint supports (we only support post or basic) */
	json_t *j_token_endpoint_auth_methods_supported = json_object_get(
			j_provider, "token_endpoint_auth_methods_supported");
	if ((j_token_endpoint_auth_methods_supported == NULL)
			|| (!json_is_array(j_token_endpoint_auth_methods_supported))) {
		oidc_debug(r,
				"provider (%s) JSON metadata did not contain a \"token_endpoint_auth_methods_supported\" array, assuming \"client_secret_basic\" is supported",
				issuer);
	} else {
		int i;
		for (i = 0;
				i < json_array_size(j_token_endpoint_auth_methods_supported);
				i++) {
			json_t *elem = json_array_get(
					j_token_endpoint_auth_methods_supported, i);
			if (!json_is_string(elem)) {
				oidc_warn(r,
						"unhandled in-array JSON object type [%d] in provider (%s) metadata for entry \"token_endpoint_auth_methods_supported\"",
						elem->type, issuer);
				continue;
			}
			if (strcmp(json_string_value(elem), "client_secret_post") == 0) {
				break;
			}
			if (strcmp(json_string_value(elem), "client_secret_basic") == 0) {
				break;
			}
		}
		if (i == json_array_size(j_token_endpoint_auth_methods_supported)) {
			oidc_error(r,
					"could not find a supported value [client_secret_post|client_secret_basic] in provider (%s) metadata for entry \"token_endpoint_auth_methods_supported\"",
					issuer);
			return FALSE;
		}
	}

	return TRUE;
}