From e1eacaa38f33d32cf36cad72037710f302f2cecd Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Thu, 6 Jun 2024 11:37:21 +0200 Subject: [PATCH] add OIDCDPoPMode [off|optional|required] primitive - store the token_type in the session - pass the token type in a header to the backend Signed-off-by: Hans Zandbelt --- ChangeLog | 4 ++++ auth_openidc.conf | 12 ++++++++++-- src/cfg/cfg.h | 1 + src/cfg/cmds.c | 5 +++++ src/cfg/provider.c | 23 +++++++++++++++++++++++ src/cfg/provider.h | 9 +++++++++ src/handle/handle.h | 11 ++++++----- src/handle/info.c | 5 ++++- src/handle/logout.c | 2 +- src/handle/refresh.c | 9 ++++++--- src/handle/response.c | 14 +++++++++----- src/handle/userinfo.c | 22 +++++++++++++--------- src/metadata.c | 14 +++++++++++++- src/mod_auth_openidc.c | 8 ++++++++ src/mod_auth_openidc.h | 1 + src/proto/dpop.c | 1 - src/proto/proto.h | 5 +++-- src/proto/token.c | 13 +++++++++++-- src/proto/userinfo.c | 13 +++++++------ src/session.c | 13 +++++++++++++ src/session.h | 2 ++ 21 files changed, 149 insertions(+), 38 deletions(-) diff --git a/ChangeLog b/ChangeLog index bef8b0d1..61aa18f7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +06/06/2024 +- add OIDCDPoPMode [off|optional|required] primitive +- store the token_type in the session + 06/05/2024 - add "nbf" claim in the Request Object as per https://openid.net/specs/openid-financial-api-part-2-1_0-final.html#rfc.section.5.2.2 diff --git a/auth_openidc.conf b/auth_openidc.conf index a9f03665..b76f7337 100644 --- a/auth_openidc.conf +++ b/auth_openidc.conf @@ -304,10 +304,18 @@ # 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) +# The PKCE method used (this serves as default value for multi-provider OPs too) # When not defined S256 is used. # NB: this can be overridden on a per-OP basis in the .conf file using the key: pkce_method -#OIDCPKCEMethod [plain|S256|none] +#OIDCPKCEMethod [ S256 | plain | |none ] + +# The DPoP mode used (this serves as default value for multi-provider OPs too) +# off: no DPoP token is requested from the OP +# optional: a DPoP token is requested from the OP but we'll continue even if the returned token is Bearer +# required: a DPoP token is requested from the OP and we'll fail if the returned token type is not DPoP +# When not defined "off" is used. +# NB: this can be overridden on a per-OP basis in the .conf file using the key: dpop_mode +#OIDCDPoPMode [ off | optional | required] # (used only in dynamic client registration) # Define the Client JWKs URL (e.g. https://localhost/protected/?jwks=rsa)") that will be diff --git a/src/cfg/cfg.h b/src/cfg/cfg.h index 14ea4fc0..62ee334e 100644 --- a/src/cfg/cfg.h +++ b/src/cfg/cfg.h @@ -117,6 +117,7 @@ typedef enum { #define OIDC_HOOK_INFO_TIMESTAMP "iat" #define OIDC_HOOK_INFO_ACCES_TOKEN "access_token" +#define OIDC_HOOK_INFO_ACCES_TOKEN_TYPE "access_token_type" #define OIDC_HOOK_INFO_ACCES_TOKEN_EXP "access_token_expires" #define OIDC_HOOK_INFO_ID_TOKEN_HINT "id_token_hint" #define OIDC_HOOK_INFO_ID_TOKEN "id_token" diff --git a/src/cfg/cmds.c b/src/cfg/cmds.c index 089f94d9..42005592 100644 --- a/src/cfg/cmds.c +++ b/src/cfg/cmds.c @@ -545,6 +545,11 @@ const command_rec oidc_cfg_cmds[] = { OIDCPKCEMethod, pkce, "The RFC 7636 PCKE mode used; must be one of \"plain\" or \"S256\""), + OIDC_CFG_CMD_PROVIDER( + AP_INIT_TAKE1, + OIDCDPoPMode, + dpop_mode, + "The RFC 9449 DPoP mode used; must be one of \"off\", \"optional\" or \"required\""), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCClientID, diff --git a/src/cfg/provider.c b/src/cfg/provider.c index 5d57acf8..f45e452d 100644 --- a/src/cfg/provider.c +++ b/src/cfg/provider.c @@ -82,6 +82,7 @@ struct oidc_provider_t { char *logout_request_params; int session_max_duration; oidc_proto_pkce_t *pkce; + oidc_dpop_mode_t dpop_mode; int userinfo_refresh_interval; apr_array_header_t *client_keys; char *client_jwks_uri; @@ -261,6 +262,26 @@ const char *oidc_cfg_provider_pkce_set(apr_pool_t *pool, oidc_provider_t *provid OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(pkce, const oidc_proto_pkce_t *, OIDC_DEFAULT_PROVIDER_PKCE) +/* + * DPoP + */ +#define OIDC_DPOP_MODE_OFF_STR "off" +#define OIDC_DPOP_MODE_OPTIONAL_STR "optional" +#define OIDC_DPOP_MODE_REQUIRED_STR "required" + +static const char *oidc_cfg_provider_parse_dop_method(apr_pool_t *pool, const char *arg, oidc_dpop_mode_t *mode) { + static const oidc_cfg_option_t options[] = { + {OIDC_DPOP_MODE_OFF, OIDC_DPOP_MODE_OFF_STR}, + {OIDC_DPOP_MODE_OPTIONAL, OIDC_DPOP_MODE_OPTIONAL_STR}, + {OIDC_DPOP_MODE_REQUIRED, OIDC_DPOP_MODE_REQUIRED_STR}, + }; + return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)mode); +} + +#define OIDC_DEFAULT_DPOP_MODE OIDC_DPOP_MODE_OFF +OIDC_PROVIDER_MEMBER_FUNCS_STR_INT(dpop_mode, oidc_cfg_provider_parse_dop_method, oidc_dpop_mode_t, + OIDC_DEFAULT_DPOP_MODE) + OIDC_PROVIDER_MEMBER_FUNCS_STR(issuer, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(authorization_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_STR(auth_request_params, NULL) @@ -619,6 +640,7 @@ static void oidc_cfg_provider_init(oidc_provider_t *provider) { provider->auth_request_params = NULL; provider->logout_request_params = NULL; provider->pkce = NULL; + provider->dpop_mode = OIDC_CONFIG_POS_INT_UNSET; provider->client_jwks_uri = NULL; provider->client_keys = NULL; @@ -711,6 +733,7 @@ void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_ dst->logout_request_params = add->logout_request_params != NULL ? add->logout_request_params : base->logout_request_params; dst->pkce = add->pkce != NULL ? add->pkce : base->pkce; + dst->dpop_mode = add->dpop_mode != OIDC_CONFIG_POS_INT_UNSET ? add->dpop_mode : base->dpop_mode; dst->client_jwks_uri = add->client_jwks_uri != NULL ? add->client_jwks_uri : base->client_jwks_uri; dst->client_keys = add->client_keys != NULL ? add->client_keys : base->client_keys; diff --git a/src/cfg/provider.h b/src/cfg/provider.h index 18f1348b..1b0f624f 100644 --- a/src/cfg/provider.h +++ b/src/cfg/provider.h @@ -74,11 +74,18 @@ typedef enum { OIDC_AUTH_REQUEST_METHOD_PAR = 3, } oidc_auth_request_method_t; +/* methods to send an access token in a userinfo request */ typedef enum { OIDC_USER_INFO_TOKEN_METHOD_HEADER = 1, OIDC_USER_INFO_TOKEN_METHOD_POST = 2, } oidc_userinfo_token_method_t; +typedef enum { + OIDC_DPOP_MODE_OFF = 1, + OIDC_DPOP_MODE_OPTIONAL = 2, + OIDC_DPOP_MODE_REQUIRED = 3, +} oidc_dpop_mode_t; + typedef struct oidc_jwks_uri_t { const char *uri; int refresh_interval; @@ -109,6 +116,7 @@ typedef struct oidc_jwks_uri_t { #define OIDCResponseType "OIDCResponseType" #define OIDCProviderAuthRequestMethod "OIDCProviderAuthRequestMethod" #define OIDCPKCEMethod "OIDCPKCEMethod" +#define OIDCDPoPMode "OIDCDPoPMode" #define OIDCResponseMode "OIDCResponseMode" #define OIDCClientJwksUri "OIDCClientJwksUri" #define OIDCIDTokenSignedResponseAlg "OIDCIDTokenSignedResponseAlg" @@ -222,6 +230,7 @@ OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(userinfo_refresh_interval, const char *) // for metadata.c OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(userinfo_token_method, oidc_userinfo_token_method_t) OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(auth_request_method, oidc_auth_request_method_t) +OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_INT_DECL(dpop_mode, oidc_dpop_mode_t) // types OIDC_CFG_PROVIDER_MEMBER_FUNCS_TYPE_DECL(pkce, const oidc_proto_pkce_t *) diff --git a/src/handle/handle.h b/src/handle/handle.h index fd26a322..90bcdfc9 100644 --- a/src/handle/handle.h +++ b/src/handle/handle.h @@ -95,7 +95,7 @@ int oidc_logout_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, // refresh.c apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, - char **new_access_token, char **new_id_token); + char **new_access_token, char **new_access_token_type, char **new_id_token); int oidc_refresh_token_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session); apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, int ttl_minimum, apr_byte_t *needs_save); @@ -117,8 +117,9 @@ int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *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); + const char *access_token_type, const int expires_in, const char *refresh_token, + const char *session_state, const char *state, const char *original_url, + const char *userinfo_jwt); // revoke.c int oidc_revoke_session(request_rec *r, oidc_cfg_t *c); @@ -131,8 +132,8 @@ int oidc_session_management(request_rec *r, oidc_cfg_t *c, oidc_session_t *sessi void oidc_userinfo_store_claims(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, const char *claims, const char *userinfo_jwt); const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, - const char *access_token, oidc_session_t *session, char *id_token_sub, - char **userinfo_jwt); + const char *access_token, const char *access_token_type, + 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); diff --git a/src/handle/info.c b/src/handle/info.c index 8f53ccee..589391c8 100644 --- a/src/handle/info.c +++ b/src/handle/info.c @@ -111,7 +111,7 @@ int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, ap return HTTP_INTERNAL_SERVER_ERROR; /* execute the actual refresh grant */ - if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL) == FALSE) { + if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, NULL) == FALSE) { oidc_warn(r, "access_token could not be refreshed"); return HTTP_INTERNAL_SERVER_ERROR; } @@ -145,6 +145,9 @@ int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, ap 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)); + const char *access_token_type = oidc_session_get_access_token_type(r, session); + if (access_token_type != NULL) + json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_TYPE, json_string(access_token_type)); } /* include the access token expiry timestamp in the session info */ diff --git a/src/handle/logout.c b/src/handle/logout.c index d148a576..c9613382 100644 --- a/src/handle/logout.c +++ b/src/handle/logout.c @@ -481,7 +481,7 @@ int oidc_logout(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { if ((provider != NULL) && (oidc_cfg_provider_end_session_endpoint_get(provider) != NULL)) { if (apr_table_get(r->subprocess_env, OIDC_REFRESH_TOKENS_BEFORE_LOGOUT_ENVVAR) != NULL) { - oidc_refresh_token_grant(r, c, session, provider, NULL, &id_token_hint); + oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, &id_token_hint); } else { id_token_hint = (char *)oidc_session_get_idtoken(r, session); } diff --git a/src/handle/refresh.c b/src/handle/refresh.c index c6f38077..ad2234fe 100644 --- a/src/handle/refresh.c +++ b/src/handle/refresh.c @@ -194,7 +194,7 @@ static apr_byte_t oidc_refresh_token_cache_get(request_rec *r, oidc_cfg_t *c, co * execute refresh token grant to refresh the existing access token */ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider, - char **new_access_token, char **new_id_token) { + char **new_access_token, char **new_access_token_type, char **new_id_token) { apr_byte_t rc = FALSE; char *s_id_token = NULL; @@ -243,6 +243,7 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_ /* 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_type(r, session, s_token_type); oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ @@ -251,6 +252,8 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_ /* see if we need to return it as a parameter */ if (new_access_token != NULL) *new_access_token = s_access_token; + if (new_access_token_type != NULL) + *new_access_token_type = s_token_type; /* if we have a new refresh token (rolling refresh), store it in the session and overwrite the old one */ if (s_refresh_token != NULL) @@ -353,7 +356,7 @@ int oidc_refresh_token_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *se } /* execute the actual refresh grant */ - if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL) == FALSE) { + if (oidc_refresh_token_grant(r, c, session, provider, NULL, NULL, NULL) == FALSE) { oidc_error(r, "access_token could not be refreshed"); error_code = "refresh_failed"; goto end; @@ -421,7 +424,7 @@ apr_byte_t oidc_refresh_access_token_before_expiry(request_rec *r, oidc_cfg_t *c if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE) return FALSE; - if (oidc_refresh_token_grant(r, cfg, session, provider, NULL, NULL) == FALSE) { + if (oidc_refresh_token_grant(r, cfg, session, provider, NULL, NULL, NULL) == FALSE) { oidc_warn(r, "access_token could not be refreshed"); *needs_save = FALSE; return FALSE; diff --git a/src/handle/response.c b/src/handle/response.c index 07be8f04..dbc3d00c 100644 --- a/src/handle/response.c +++ b/src/handle/response.c @@ -225,8 +225,9 @@ char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const c apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *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) { + const char *access_token_type, 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 = apr_pstrdup(r->pool, remoteUser); @@ -278,6 +279,8 @@ apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_ses if (access_token != NULL) { /* store the access_token in the session context */ oidc_session_set_access_token(r, session, access_token); + /* store the access_token in the session context */ + oidc_session_set_access_token_type(r, session, access_token_type); /* store the associated expires_in value */ oidc_session_set_access_token_expires(r, session, expires_in); /* reset the access token refresh timestamp */ @@ -614,7 +617,8 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t * * parsed claims are not actually used here but need to be parsed anyway for error checking purposes */ const char *claims = oidc_userinfo_retrieve_claims( - r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL, jwt->payload.sub, &userinfo_jwt); + r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), + apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), 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); @@ -644,8 +648,8 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t * /* store resolved information in the session */ if (oidc_response_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_ACCESS_TOKEN), apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), + 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) { oidc_proto_state_destroy(proto_state); diff --git a/src/handle/userinfo.c b/src/handle/userinfo.c index b9fd96f3..f68d6684 100644 --- a/src/handle/userinfo.c +++ b/src/handle/userinfo.c @@ -85,11 +85,12 @@ void oidc_userinfo_store_claims(request_rec *r, oidc_cfg_t *c, oidc_session_t *s * retrieve claims from the userinfo endpoint and return the stringified response */ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider, - const char *access_token, oidc_session_t *session, char *id_token_sub, - char **userinfo_jwt) { + const char *access_token, const char *access_token_type, + oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) { char *result = NULL; char *refreshed_access_token = NULL; + char *refreshed_access_token_type = NULL; json_t *id_token_claims = NULL; long response_code = 0; @@ -122,8 +123,8 @@ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_pr // routines) /* try to get claims from the userinfo endpoint using the provided access token */ - if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, access_token, &result, userinfo_jwt, - &response_code) == TRUE) + if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, access_token, access_token_type, &result, + userinfo_jwt, &response_code) == TRUE) goto end; /* see if this is the initial call to the user info endpoint upon receiving the authorization response */ @@ -144,7 +145,8 @@ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_pr /* first call to user info endpoint failed, but this is for an existing session and the access token may have * just expired, so refresh it */ - if (oidc_refresh_token_grant(r, c, session, provider, &refreshed_access_token, NULL) == FALSE) { + if (oidc_refresh_token_grant(r, c, session, provider, &refreshed_access_token, &refreshed_access_token_type, + NULL) == FALSE) { oidc_error(r, "refreshing access token failed, claims will not be retrieved/refreshed from the " "userinfo endpoint"); result = NULL; @@ -152,8 +154,8 @@ const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_pr } /* try again with the new access token */ - if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, refreshed_access_token, &result, userinfo_jwt, - NULL) == FALSE) { + if (oidc_proto_userinfo_request(r, c, provider, id_token_sub, refreshed_access_token, + refreshed_access_token_type, &result, userinfo_jwt, NULL) == FALSE) { oidc_error(r, "resolving user info claims with the refreshed access token failed, nothing will be " "stored in the session"); @@ -181,6 +183,7 @@ apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_se oidc_provider_t *provider = NULL; const char *claims = NULL; const char *access_token = NULL; + const char *access_token_type = NULL; char *userinfo_jwt = NULL; /* see int we can do anything here, i.e. a refresh interval is configured */ @@ -212,10 +215,11 @@ apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_se /* get the current access token */ access_token = oidc_session_get_access_token(r, session); + access_token_type = oidc_session_get_access_token_type(r, session); /* retrieve the current claims */ - claims = oidc_userinfo_retrieve_claims(r, cfg, provider, access_token, session, NULL, - &userinfo_jwt); + claims = oidc_userinfo_retrieve_claims(r, cfg, provider, access_token, + access_token_type, session, NULL, &userinfo_jwt); /* store claims resolved from userinfo endpoint */ oidc_userinfo_store_claims(r, cfg, session, provider, claims, userinfo_jwt); diff --git a/src/metadata.c b/src/metadata.c index 6895e3b7..aa6969cc 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -110,6 +110,7 @@ #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_DPOP_MODE "dpop_mode" #define OIDC_METADATA_CLIENT_CONTACT "client_contact" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH "token_endpoint_auth" #define OIDC_METADATA_REGISTRATION_TOKEN "registration_token" @@ -1338,6 +1339,17 @@ apr_byte_t oidc_metadata_conf_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_c : OIDC_PKCE_METHOD_NONE); OIDC_METADATA_PROVIDER_SET(pkce, value, rv) + /* see if we've got a custom DPoP mode */ + oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_DPOP_MODE, &value, NULL); + if (value) { + rv = oidc_cfg_provider_dpop_mode_set(r->pool, provider, value); + if (rv != NULL) + oidc_error(r, "oidc_cfg_provider_dpop_mode_set: %s", rv); + } else { + oidc_cfg_provider_dpop_mode_int_set(provider, + oidc_cfg_provider_dpop_mode_get(oidc_cfg_provider_get(cfg))); + } + /* get the client name */ oidc_util_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_NAME, &value, oidc_cfg_provider_client_name_get(oidc_cfg_provider_get(cfg))); @@ -1402,7 +1414,7 @@ apr_byte_t oidc_metadata_conf_parse(request_rec *r, oidc_cfg_t *cfg, json_t *j_c if (value) { rv = oidc_cfg_provider_userinfo_token_method_set(r->pool, provider, value); if (rv != NULL) - oidc_error(r, "oidc_cfg_provider_userinfo_token_method_get: %s", rv); + oidc_error(r, "oidc_cfg_provider_userinfo_token_method_set: %s", rv); } else { oidc_cfg_provider_userinfo_token_method_int_set( provider, oidc_cfg_provider_userinfo_token_method_get(oidc_cfg_provider_get(cfg))); diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c index 72e2a5a2..b7b667e0 100644 --- a/src/mod_auth_openidc.c +++ b/src/mod_auth_openidc.c @@ -797,6 +797,14 @@ apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg_t *cfg, oidc_sessio encoding); } + /* set the access_token type in the app headers/variables */ + const char *access_token_type = oidc_session_get_access_token_type(r, session); + if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_type != NULL) { + /* pass it to the app in a header or environment variable */ + oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_TYPE, access_token, OIDC_DEFAULT_HEADER_PREFIX, + pass_in, encoding); + } + /* set the expiry timestamp in the app headers/variables */ const char *access_token_expires = oidc_session_get_access_token_expires2str(r, session); if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && access_token_expires != NULL) { diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h index ab1658b3..7d0abfc0 100644 --- a/src/mod_auth_openidc.h +++ b/src/mod_auth_openidc.h @@ -122,6 +122,7 @@ #define OIDC_APP_INFO_REFRESH_TOKEN "refresh_token" #define OIDC_APP_INFO_ACCESS_TOKEN "access_token" +#define OIDC_APP_INFO_ACCESS_TOKEN_TYPE "access_token_type" #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" diff --git a/src/proto/dpop.c b/src/proto/dpop.c index 99ae6f85..8126aaf9 100644 --- a/src/proto/dpop.c +++ b/src/proto/dpop.c @@ -63,7 +63,6 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), -1, OIDC_JOSE_JWK_SIG_STR); if (jwk == NULL) { - // TODO: may become an error once DPoP is required by config oidc_debug(r, "no RSA/EC private signing keys have been configured (in " OIDCPrivateKeyFiles ")"); goto end; } diff --git a/src/proto/proto.h b/src/proto/proto.h index e5785cb5..9d328739 100644 --- a/src/proto/proto.h +++ b/src/proto/proto.h @@ -243,7 +243,8 @@ apr_byte_t oidc_proto_token_refresh_request(request_rec *r, oidc_cfg_t *cfg, oid // userinfo.c apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, - const char *id_token_sub, const char *access_token, char **response, - char **userinfo_jwt, long *response_code); + const char *id_token_sub, const char *access_token, + const char *access_token_type, char **response, char **userinfo_jwt, + long *response_code); #endif /* _MOD_AUTH_OPENIDC_PROTO_H_ */ diff --git a/src/proto/token.c b/src/proto/token.c index 272e7162..b579ba49 100644 --- a/src/proto/token.c +++ b/src/proto/token.c @@ -58,6 +58,7 @@ static apr_byte_t oidc_proto_validate_token_type(request_rec *r, oidc_provider_t oidc_cfg_provider_issuer_get(provider), OIDC_PROTO_BEARER, OIDC_PROTO_DPOP); return FALSE; } + return TRUE; } @@ -85,7 +86,7 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi oidc_util_table_add_query_encoded_params(r->pool, params, oidc_cfg_provider_token_endpoint_params_get(provider)); - if (oidc_cfg_provider_response_require_iss_get(provider)) + if (oidc_cfg_provider_dpop_mode_get(provider) != OIDC_DPOP_MODE_OFF) dpop = oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL); /* send the request to the token endpoint */ @@ -114,11 +115,19 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi /* get the token type from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_TOKEN_TYPE, token_type, NULL); + /* check if DPoP is required */ + if ((oidc_cfg_provider_dpop_mode_get(provider) == OIDC_DPOP_MODE_REQUIRED) && + (_oidc_strnatcasecmp(*token_type, OIDC_PROTO_DPOP) != 0)) { + oidc_error(r, "access token type is \"%s\" but \"%s\" is required", *token_type, OIDC_PROTO_DPOP); + return FALSE; + } + /* 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"); + oidc_warn(r, "access token type \"%s\" did not validate, dropping it", *token_type); *access_token = NULL; + *token_type = NULL; } } diff --git a/src/proto/userinfo.c b/src/proto/userinfo.c index e92991ac..952b8646 100644 --- a/src/proto/userinfo.c +++ b/src/proto/userinfo.c @@ -239,18 +239,19 @@ static apr_byte_t oidc_proto_userinfo_request_composite_claims(request_rec *r, o * get claims from the OP UserInfo endpoint using the provided access_token */ apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, - const char *id_token_sub, const char *access_token, char **response, - char **userinfo_jwt, long *response_code) { + const char *id_token_sub, const char *access_token, + const char *access_token_type, char **response, char **userinfo_jwt, + long *response_code) { char *dpop = NULL; - oidc_debug(r, "enter, endpoint=%s, access_token=%s", oidc_cfg_provider_userinfo_endpoint_url_get(provider), - access_token); + oidc_debug(r, "enter, endpoint=%s, access_token=%s, token_type=%s", + oidc_cfg_provider_userinfo_endpoint_url_get(provider), access_token, access_token_type); OIDC_METRICS_TIMING_START(r, cfg); /* get the JSON response */ if (oidc_cfg_provider_userinfo_token_method_get(provider) == OIDC_USER_INFO_TOKEN_METHOD_HEADER) { - if (oidc_cfg_provider_response_require_iss_get(provider)) + if (_oidc_strnatcasecmp(access_token_type, OIDC_PROTO_DPOP) == 0) dpop = oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_userinfo_endpoint_url_get(provider), "GET", access_token); if (oidc_http_get(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), NULL, NULL, access_token, @@ -263,7 +264,7 @@ apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_pro } else if (oidc_cfg_provider_userinfo_token_method_get(provider) == 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_cfg_provider_response_require_iss_get(provider)) + if (_oidc_strnatcasecmp(access_token_type, OIDC_PROTO_DPOP) == 0) dpop = oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_userinfo_endpoint_url_get(provider), "POST", access_token); if (oidc_http_post_form(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), params, NULL, NULL, diff --git a/src/session.c b/src/session.c index 7f978116..d261066b 100644 --- a/src/session.c +++ b/src/session.c @@ -449,6 +449,8 @@ apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, #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 type in the session context */ +#define OIDC_SESSION_KEY_ACCESSTOKEN_TYPE "att" /* 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 */ @@ -626,6 +628,17 @@ 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 type + */ +void oidc_session_set_access_token_type(request_rec *r, oidc_session_t *z, const char *token_type) { + oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE, token_type); +} + +const char *oidc_session_get_access_token_type(request_rec *r, oidc_session_t *z) { + return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_TYPE); +} + /* * access token expires */ diff --git a/src/session.h b/src/session.h index 9daf484f..37eb0ae5 100644 --- a/src/session.h +++ b/src/session.h @@ -81,6 +81,8 @@ void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_i 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_type(request_rec *r, oidc_session_t *z, const char *token_type); +const char *oidc_session_get_access_token_type(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); apr_time_t oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z); const char *oidc_session_get_access_token_expires2str(request_rec *r, oidc_session_t *z);