diff --git a/src/handle/handle.h b/src/handle/handle.h index f20c2002..a063fa88 100644 --- a/src/handle/handle.h +++ b/src/handle/handle.h @@ -43,6 +43,7 @@ #ifndef _MOD_AUTH_OPENIDC_HANDLE_H_ #define _MOD_AUTH_OPENIDC_HANDLE_H_ +#include "cfg/dir.h" #include "const.h" // for the PACKAGE_* defines #include "jose.h" #include "session.h" @@ -136,5 +137,7 @@ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_pr oidc_session_t *session, char *id_token_sub, char **userinfo_jwt); apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, apr_byte_t *needs_save); +void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, + oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding); #endif // _MOD_AUTH_OPENIDC_HANDLE_H_ diff --git a/src/handle/userinfo.c b/src/handle/userinfo.c index f68d6684..90ebcfe1 100644 --- a/src/handle/userinfo.c +++ b/src/handle/userinfo.c @@ -44,6 +44,7 @@ #include "handle/handle.h" #include "mod_auth_openidc.h" #include "proto/proto.h" +#include "util.h" /* * store claims resolved from the userinfo endpoint in the session @@ -239,3 +240,182 @@ apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_se return rc; } + +#define OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT 60 +#define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT -1 +#define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL" + +/* + * obtain the signed JWT cache TTL from the environment variables + */ +static int oidc_userinfo_signed_jwt_cache_ttl(request_rec *r) { + const char *s_ttl = apr_table_get(r->subprocess_env, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR); + return _oidc_str_to_int(s_ttl, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT); +} + +/* + * create a signed JWT with s_claims payload and return the serialized form in cser + */ +static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, + const char *s_claims, char **cser) { + apr_byte_t rv = FALSE; + oidc_jwt_t *jwt = NULL; + oidc_jwk_t *jwk = NULL; + oidc_jose_error_t err; + apr_time_t access_token_expires = -1; + char *jti = NULL; + char *key = NULL; + json_t *json = NULL; + int ttl = 0; + int exp = 0; + apr_time_t expiry = 0; + + oidc_debug(r, "enter: %s", s_claims); + + if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, FALSE) == FALSE) + goto end; + + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, + json_string(oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(cfg)))); + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, + json_string(oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)))); + + oidc_util_decode_json_object(r, s_claims, &json); + if (json == NULL) + goto end; + if (oidc_util_json_merge(r, json, jwt->payload.value.json) == FALSE) + goto end; + s_claims = oidc_util_encode_json_object(r, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); + if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, s_claims, _oidc_strlen(s_claims) + 1, + &key, &err) == FALSE) { + oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); + goto end; + } + + ttl = oidc_userinfo_signed_jwt_cache_ttl(r); + if (ttl > -1) + oidc_cache_get_signed_jwt(r, key, cser); + + if (*cser != NULL) { + oidc_debug(r, "signed JWT found in cache"); + rv = TRUE; + goto end; + } + + if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_JTI) == NULL) { + oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); + } + if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_IAT) == NULL) { + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, + json_integer(apr_time_sec(apr_time_now()))); + } + if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_EXP) == NULL) { + access_token_expires = oidc_session_get_access_token_expires(r, session); + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP, + json_integer(access_token_expires > 0 ? apr_time_sec(access_token_expires) + : apr_time_sec(apr_time_now()) + + OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT)); + } + + if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, cser) == FALSE) + goto end; + + rv = TRUE; + + if (ttl < 0) + goto end; + + if (ttl == 0) { + // need to get the cache ttl from the exp claim + oidc_util_json_object_get_int(jwt->payload.value.json, OIDC_CLAIM_EXP, &exp, 0); + // actually the exp claim always exists by now + expiry = (exp > 0) ? apr_time_from_sec(exp) + : apr_time_now() + apr_time_from_sec(OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT); + } else { + // ttl > 0 + expiry = apr_time_now() + apr_time_from_sec(ttl); + } + + oidc_debug(r, "caching signed JWT with ~ttl(%ld)", apr_time_sec(expiry - apr_time_now())); + oidc_cache_set_signed_jwt(r, key, *cser, expiry); + +end: + + if (json) + json_decref(json); + + if (jwt) + oidc_jwt_destroy(jwt); + + return rv; +} + +/* + * pass the userinfo claims to headers and/or environment variables, encoded as configured + */ +void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, + oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { + const apr_array_header_t *pass_userinfo_as = NULL; + oidc_pass_user_info_as_t *p = NULL; + int i = 0; + char *cser = NULL; + + pass_userinfo_as = oidc_cfg_dir_pass_userinfo_as_get(r); + +#ifdef USE_LIBJQ + s_claims = oidc_util_jq_filter(r, s_claims, oidc_cfg_dir_userinfo_claims_expr_get(r)); +#endif + + for (i = 0; (pass_userinfo_as != NULL) && (i < pass_userinfo_as->nelts); i++) { + + p = APR_ARRAY_IDX(pass_userinfo_as, i, oidc_pass_user_info_as_t *); + + switch (p->type) { + + case OIDC_PASS_USERINFO_AS_CLAIMS: + /* set the userinfo claims in the app headers */ + oidc_set_app_claims(r, cfg, s_claims); + break; + + case 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, p->name ? p->name : OIDC_APP_INFO_USERINFO_JSON, s_claims, + p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); + break; + + case OIDC_PASS_USERINFO_AS_JWT: + if (oidc_cfg_session_type_get(cfg) != 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, p->name ? p->name : OIDC_APP_INFO_USERINFO_JWT, s_userinfo_jwt, + p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); + } 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"); + } + break; + + case OIDC_PASS_USERINFO_AS_SIGNED_JWT: + + if (oidc_userinfo_create_signed_jwt(r, cfg, session, s_claims, &cser) == TRUE) { + oidc_util_set_app_info(r, p->name ? p->name : OIDC_APP_INFO_SIGNED_JWT, cser, + p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); + } + break; + + default: + break; + } + } +} diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c index 7ffacf7f..49d0eb4b 100644 --- a/src/mod_auth_openidc.c +++ b/src/mod_auth_openidc.c @@ -566,7 +566,7 @@ const char *oidc_request_state_get(request_rec *r, const char *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, oidc_cfg_t *cfg, const char *s_claims) { +apr_byte_t oidc_set_app_claims(request_rec *r, oidc_cfg_t *cfg, const char *s_claims) { json_t *j_claims = NULL; @@ -842,185 +842,6 @@ apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg_t *cfg, oidc_sessio return TRUE; } -#define OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT 60 -#define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT -1 -#define OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR "OIDC_USERINFO_SIGNED_JWT_CACHE_TTL" - -/* - * obtain the signed JWT cache TTL from the environment variables - */ -static int oidc_userinfo_signed_jwt_cache_ttl(request_rec *r) { - const char *s_ttl = apr_table_get(r->subprocess_env, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_ENVVAR); - return _oidc_str_to_int(s_ttl, OIDC_USERINFO_SIGNED_JWT_CACHE_TTL_DEFAULT); -} - -/* - * create a signed JWT with s_claims payload and return the serialized form in cser - */ -static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, - const char *s_claims, char **cser) { - apr_byte_t rv = FALSE; - oidc_jwt_t *jwt = NULL; - oidc_jwk_t *jwk = NULL; - oidc_jose_error_t err; - apr_time_t access_token_expires = -1; - char *jti = NULL; - char *key = NULL; - json_t *json = NULL; - int ttl = 0; - int exp = 0; - apr_time_t expiry = 0; - - oidc_debug(r, "enter: %s", s_claims); - - if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, FALSE) == FALSE) - goto end; - - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, - json_string(oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(cfg)))); - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, - json_string(oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)))); - - oidc_util_decode_json_object(r, s_claims, &json); - if (json == NULL) - goto end; - if (oidc_util_json_merge(r, json, jwt->payload.value.json) == FALSE) - goto end; - s_claims = oidc_util_encode_json_object(r, jwt->payload.value.json, JSON_PRESERVE_ORDER | JSON_COMPACT); - if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, s_claims, _oidc_strlen(s_claims) + 1, - &key, &err) == FALSE) { - oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); - goto end; - } - - ttl = oidc_userinfo_signed_jwt_cache_ttl(r); - if (ttl > -1) - oidc_cache_get_signed_jwt(r, key, cser); - - if (*cser != NULL) { - oidc_debug(r, "signed JWT found in cache"); - rv = TRUE; - goto end; - } - - if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_JTI) == NULL) { - oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); - } - if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_IAT) == NULL) { - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, - json_integer(apr_time_sec(apr_time_now()))); - } - if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_EXP) == NULL) { - access_token_expires = oidc_session_get_access_token_expires(r, session); - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP, - json_integer(access_token_expires > 0 ? apr_time_sec(access_token_expires) - : apr_time_sec(apr_time_now()) + - OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT)); - } - - if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, cser) == FALSE) - goto end; - - rv = TRUE; - - if (ttl < 0) - goto end; - - if (ttl == 0) { - // need to get the cache ttl from the exp claim - oidc_util_json_object_get_int(jwt->payload.value.json, OIDC_CLAIM_EXP, &exp, 0); - // actually the exp claim always exists by now - expiry = (exp > 0) ? apr_time_from_sec(exp) - : apr_time_now() + apr_time_from_sec(OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT); - } else { - // ttl > 0 - expiry = apr_time_now() + apr_time_from_sec(ttl); - } - - oidc_debug(r, "caching signed JWT with ~ttl(%ld)", apr_time_sec(expiry - apr_time_now())); - oidc_cache_set_signed_jwt(r, key, *cser, expiry); - -end: - - if (json) - json_decref(json); - - if (jwt) - oidc_jwt_destroy(jwt); - - return rv; -} - -/* - * pass the userinfo claims to headers and/or environment variables, encoded as configured - */ -static void oidc_pass_userinfo_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims, - oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding) { - const apr_array_header_t *pass_userinfo_as = NULL; - oidc_pass_user_info_as_t *p = NULL; - int i = 0; - char *cser = NULL; - - pass_userinfo_as = oidc_cfg_dir_pass_userinfo_as_get(r); - -#ifdef USE_LIBJQ - s_claims = oidc_util_jq_filter(r, s_claims, oidc_cfg_dir_userinfo_claims_expr_get(r)); -#endif - - for (i = 0; (pass_userinfo_as != NULL) && (i < pass_userinfo_as->nelts); i++) { - - p = APR_ARRAY_IDX(pass_userinfo_as, i, oidc_pass_user_info_as_t *); - - switch (p->type) { - - case OIDC_PASS_USERINFO_AS_CLAIMS: - /* set the userinfo claims in the app headers */ - oidc_set_app_claims(r, cfg, s_claims); - break; - - case 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, p->name ? p->name : OIDC_APP_INFO_USERINFO_JSON, s_claims, - p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); - break; - - case OIDC_PASS_USERINFO_AS_JWT: - if (oidc_cfg_session_type_get(cfg) != 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, p->name ? p->name : OIDC_APP_INFO_USERINFO_JWT, s_userinfo_jwt, - p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); - } 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"); - } - break; - - case OIDC_PASS_USERINFO_AS_SIGNED_JWT: - - if (oidc_userinfo_create_signed_jwt(r, cfg, session, s_claims, &cser) == TRUE) { - oidc_util_set_app_info(r, p->name ? p->name : OIDC_APP_INFO_SIGNED_JWT, cser, - p->name ? "" : OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding); - } - break; - - default: - break; - } - } -} - /* * handle the case where we have identified an existing authentication session for a user */ @@ -1136,7 +957,7 @@ static int oidc_handle_existing_session(request_rec *r, oidc_cfg_t *cfg, oidc_se if (oidc_session_pass_tokens(r, cfg, session, needs_save) == FALSE) return HTTP_INTERNAL_SERVER_ERROR; - oidc_pass_userinfo_as(r, cfg, session, s_claims, pass_in, encoding); + oidc_userinfo_pass_as(r, cfg, session, s_claims, pass_in, encoding); /* return "user authenticated" status */ return OK; diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h index 7d0abfc0..71e6a6fc 100644 --- a/src/mod_auth_openidc.h +++ b/src/mod_auth_openidc.h @@ -157,5 +157,6 @@ char *oidc_get_browser_state_hash(request_rec *r, oidc_cfg_t *c, const char *non apr_byte_t oidc_is_auth_capable_request(request_rec *r); apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg_t *c, const char *redirect_to_url, apr_byte_t restrict_to_host, char **err_str, char **err_desc); +apr_byte_t oidc_set_app_claims(request_rec *r, oidc_cfg_t *cfg, const char *s_claims); #endif /* _MOD_AUTH_OPENIDC_H_ */