From edc5ef8f7975a48b1546093406e6525c8b759292 Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Fri, 31 May 2024 01:53:26 +0200 Subject: [PATCH] add support for RFC 9126 OAuth 2.0 Pushed Authorization Requests Signed-off-by: Hans Zandbelt --- ChangeLog | 3 +++ README.md | 5 ++-- auth_openidc.conf | 8 +++++- src/cfg/cmds.c | 5 ++++ src/cfg/provider.c | 14 +++++++++-- src/cfg/provider.h | 3 +++ src/metadata.c | 7 ++++++ src/proto.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 103 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index eb9544a7..16421db4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +05/30/2024 +- add support for RFC 9126 OAuth 2.0 Pushed Authorization Requests + 04/23/2024 - disable support for the RSA PKCS v1.5 JWE encryption algorithm as it is deemed unsafe due to the Marvin attack and is removed from libcjose as well diff --git a/README.md b/README.md index 3a3ab245..1a840103 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,10 @@ Interoperability - [OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) *(Basic, Implicit, Hybrid and Refresh flows)* - [OpenID Connect Discovery 1.0](http://openid.net/specs/openid-connect-discovery-1_0.html) - [OpenID Connect Dynamic Client Registration 1.0](http://openid.net/specs/openid-connect-registration-1_0.html) -- [OAuth 2.0 Multiple Response Type Encoding Practices 1.0](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) +- [RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636) +- [RFC 9126 - OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126) - [OAuth 2.0 Form Post Response Mode 1.0](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) -- [RFC7 7636 - Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) +- [OAuth 2.0 Multiple Response Type Encoding Practices 1.0](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) - [OpenID Connect Session Management 1.0](http://openid.net/specs/openid-connect-session-1_0.html) *see the [Wiki](https://github.com/OpenIDC/mod_auth_openidc/wiki/OpenID-Connect-Session-Management) for information on how to configure it)* - [OpenID Connect Front-Channel Logout 1.0](http://openid.net/specs/openid-connect-frontchannel-1_0.html) - [OpenID Connect Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0.html) diff --git a/auth_openidc.conf b/auth_openidc.conf index 2301f6b7..a9f03665 100644 --- a/auth_openidc.conf +++ b/auth_openidc.conf @@ -139,6 +139,11 @@ # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. #OIDCProviderRevocationEndpoint +# The RFC 9126 Pushed Authorization Request endpoint URL. +# When not defined, PAR cannot be used to send authentication requests, see also OIDCProviderAuthRequestMethod +# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. +#OIDCProviderPushedAuthorizationRequestEndpoint + # Define whether the OP supports OpenID Connect Back Channel Logout. # According to: https://openid.net/specs/openid-connect-backchannel-1_0.html # Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it. @@ -214,9 +219,10 @@ # 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 +# "PAR" means that parameters will be sent to the Pushed Authorization Endpoint # When not defined the default is "GET". # NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_method -# OIDCProviderAuthRequestMethod [ GET | POST ] +# OIDCProviderAuthRequestMethod [ GET | POST | PAR ] # The fully qualified names of the files that contain the PEM-formatted RSA/EC Public key or a X.509 certificates # that contain the RSA/EC public keys to be used for JWT (OP state/id_token) encryption by the OP. diff --git a/src/cfg/cmds.c b/src/cfg/cmds.c index 056e4c9b..089f94d9 100644 --- a/src/cfg/cmds.c +++ b/src/cfg/cmds.c @@ -405,6 +405,11 @@ const command_rec oidc_cfg_cmds[] = { OIDCProviderRevocationEndpoint, revocation_endpoint_url, "Define the RFC 7009 Token Revocation Endpoint URL (e.g.: https://localhost:9031/as/revoke_token.oauth2)"), + OIDC_CFG_CMD_PROVIDER( + AP_INIT_TAKE1, + OIDCProviderPushedAuthorizationRequestEndpoint, + pushed_authorization_request_endpoint_url, + "Define the OAuth 2.0 Pushed Authorization Endpoint URL (e.g.: https://localhost:9031/as/par.oauth2)"), OIDC_CFG_CMD_PROVIDER( AP_INIT_TAKE1, OIDCProviderCheckSessionIFrame, diff --git a/src/cfg/provider.c b/src/cfg/provider.c index 0bf2a1d8..d36bb74f 100644 --- a/src/cfg/provider.c +++ b/src/cfg/provider.c @@ -55,6 +55,7 @@ struct oidc_provider_t { char *userinfo_endpoint_url; char *revocation_endpoint_url; char *registration_endpoint_url; + char *pushed_authorization_request_endpoint_url; char *check_session_iframe; char *end_session_endpoint; oidc_jwks_uri_t jwks_uri; @@ -267,6 +268,7 @@ OIDC_PROVIDER_MEMBER_FUNCS_URL(token_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_STR(token_endpoint_params, NULL) OIDC_PROVIDER_MEMBER_FUNCS_URL(userinfo_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_URL(registration_endpoint_url) +OIDC_PROVIDER_MEMBER_FUNCS_URL(pushed_authorization_request_endpoint_url) OIDC_PROVIDER_MEMBER_FUNCS_URL(check_session_iframe) OIDC_PROVIDER_MEMBER_FUNCS_URL(end_session_endpoint) OIDC_PROVIDER_MEMBER_FUNCS_STR(client_contact, NULL) @@ -321,11 +323,15 @@ OIDC_PROVIDER_MEMBER_FUNCS_PARSE_STR(userinfo_encrypted_response_enc, oidc_cfg_p #define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET" #define OIDC_AUTH_REQUEST_METHOD_POST_STR "POST" +#define OIDC_AUTH_REQUEST_METHOD_PAR_STR "PAR" static const char *oidc_cfg_provider_parse_auth_request_method(apr_pool_t *pool, const char *arg, oidc_auth_request_method_t *method) { - static const oidc_cfg_option_t options[] = {{OIDC_AUTH_REQUEST_METHOD_GET, OIDC_AUTH_REQUEST_METHOD_GET_STR}, - {OIDC_AUTH_REQUEST_METHOD_POST, OIDC_AUTH_REQUEST_METHOD_POST_STR}}; + static const oidc_cfg_option_t options[] = { + {OIDC_AUTH_REQUEST_METHOD_GET, OIDC_AUTH_REQUEST_METHOD_GET_STR}, + {OIDC_AUTH_REQUEST_METHOD_POST, OIDC_AUTH_REQUEST_METHOD_POST_STR}, + {OIDC_AUTH_REQUEST_METHOD_PAR, OIDC_AUTH_REQUEST_METHOD_PAR_STR}, + }; return oidc_cfg_parse_option(pool, options, OIDC_CFG_OPTIONS_SIZE(options), arg, (int *)method); } @@ -590,6 +596,7 @@ static void oidc_cfg_provider_init(oidc_provider_t *provider) { provider->token_endpoint_tls_client_key_pwd = NULL; provider->registration_endpoint_url = NULL; provider->registration_endpoint_json = NULL; + provider->pushed_authorization_request_endpoint_url = NULL; provider->check_session_iframe = NULL; provider->end_session_endpoint = NULL; provider->jwks_uri.uri = NULL; @@ -673,6 +680,9 @@ void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_ add->registration_endpoint_url != NULL ? add->registration_endpoint_url : base->registration_endpoint_url; dst->registration_endpoint_json = add->registration_endpoint_json != NULL ? add->registration_endpoint_json : base->registration_endpoint_json; + dst->pushed_authorization_request_endpoint_url = add->pushed_authorization_request_endpoint_url != NULL + ? add->pushed_authorization_request_endpoint_url + : base->pushed_authorization_request_endpoint_url; dst->check_session_iframe = add->check_session_iframe != NULL ? add->check_session_iframe : base->check_session_iframe; diff --git a/src/cfg/provider.h b/src/cfg/provider.h index 779f72fd..261e3288 100644 --- a/src/cfg/provider.h +++ b/src/cfg/provider.h @@ -71,6 +71,7 @@ typedef struct oidc_proto_pkce_t { typedef enum { OIDC_AUTH_REQUEST_METHOD_GET = 1, OIDC_AUTH_REQUEST_METHOD_POST = 2, + OIDC_AUTH_REQUEST_METHOD_PAR = 3, } oidc_auth_request_method_t; typedef enum { @@ -98,6 +99,7 @@ typedef struct oidc_jwks_uri_t { #define OIDCProviderRegistrationEndpointJson "OIDCProviderRegistrationEndpointJson" #define OIDCProviderUserInfoEndpoint "OIDCProviderUserInfoEndpoint" #define OIDCProviderRevocationEndpoint "OIDCProviderRevocationEndpoint" +#define OIDCProviderPushedAuthorizationRequestEndpoint "OIDCProviderPushedAuthorizationRequestEndpoint" #define OIDCProviderCheckSessionIFrame "OIDCProviderCheckSessionIFrame" #define OIDCProviderEndSessionEndpoint "OIDCProviderEndSessionEndpoint" #define OIDCProviderBackChannelLogoutSupported "OIDCProviderBackChannelLogoutSupported" @@ -176,6 +178,7 @@ OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(token_endpoint_params) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(userinfo_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(revocation_endpoint_url) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(registration_endpoint_url) +OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(pushed_authorization_request_endpoint_url); OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(check_session_iframe) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(end_session_endpoint) OIDC_CFG_PROVIDER_MEMBER_FUNCS_STR_DECL(client_id) diff --git a/src/metadata.c b/src/metadata.c index be3e0a63..5c868262 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -65,6 +65,7 @@ #define OIDC_METADATA_INTROSPECTION_ENDPOINT "introspection_endpoint" #define OIDC_METADATA_USERINFO_ENDPOINT "userinfo_endpoint" #define OIDC_METADATA_REVOCATION_ENDPOINT "revocation_endpoint" +#define OIDC_METADATA_PAR_ENDPOINT "pushed_authorization_request_endpoint" #define OIDC_METADATA_JWKS_URI "jwks_uri" #define OIDC_METADATA_SIGNED_JWKS_URI "signed_jwks_uri" #define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "token_endpoint_auth_methods_supported" @@ -1049,6 +1050,12 @@ apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg_t *cfg, json_t OIDC_METADATA_PROVIDER_SET(revocation_endpoint_url, value, rv) } + if (oidc_cfg_provider_pushed_authorization_request_endpoint_url_get(provider) == NULL) { + oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), + j_provider, OIDC_METADATA_PAR_ENDPOINT, &value, NULL); + OIDC_METADATA_PROVIDER_SET(pushed_authorization_request_endpoint_url, value, rv) + } + if (oidc_cfg_provider_jwks_uri_uri_get(provider) == NULL) { oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER, oidc_cfg_provider_issuer_get(provider), j_provider, OIDC_METADATA_JWKS_URI, &value, NULL); diff --git a/src/proto.c b/src/proto.c index 80cac1ce..586f4ac5 100644 --- a/src/proto.c +++ b/src/proto.c @@ -132,6 +132,65 @@ static void oidc_proto_auth_request_params_add(request_rec *r, apr_table_t *para } } +/* + * send a Pushed Authorization Request tot the Provider + */ +static int oidc_proto_pushed_authorization_request(request_rec *r, struct oidc_provider_t *provider, + apr_table_t *params) { + oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); + char *response = NULL, *basic_auth = NULL, *bearer_auth = NULL; + char *request_uri = NULL; + int expires_in = 0; + char *authorization_request = NULL; + json_t *j_result = NULL; + int rv = HTTP_INTERNAL_SERVER_ERROR; + const char *endpoint_url = oidc_cfg_provider_pushed_authorization_request_endpoint_url_get(provider); + + oidc_debug(r, "enter"); + + if (endpoint_url == NULL) { + oidc_error(r, "the Provider's OAuth 2.0 Pushed Authorization Request endpoint URL is not set, PAR " + "cannot be used"); + goto out; + } + + /* add the token endpoint authentication credentials to the pushed authorization request */ + if (oidc_proto_token_endpoint_auth( + r, cfg, oidc_cfg_provider_token_endpoint_auth_get(provider), oidc_cfg_provider_client_id_get(provider), + oidc_cfg_provider_client_secret_get(provider), oidc_cfg_provider_client_keys_get(provider), + oidc_cfg_provider_token_endpoint_url_get(provider), params, NULL, &basic_auth, &bearer_auth) == FALSE) + goto out; + + if (oidc_http_post_form(r, endpoint_url, params, basic_auth, bearer_auth, + oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, + oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), + oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) + goto out; + + /* check for errors, the response itself will have been logged already */ + if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) + goto out; + + /* get the request_uri from the parsed response */ + oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_REQUEST_URI, &request_uri, NULL); + + /* get the expires_in value from the parsed response */ + oidc_util_json_object_get_int(j_result, OIDC_PROTO_EXPIRES_IN, &expires_in, 60); + + /* assemble the resulting authentication request and redirect */ + apr_table_clear(params); + apr_table_setn(params, OIDC_PROTO_CLIENT_ID, oidc_cfg_provider_client_id_get(provider)); + apr_table_setn(params, OIDC_PROTO_REQUEST_URI, request_uri); + authorization_request = + oidc_http_query_encoded_url(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params); + oidc_http_hdr_out_location_set(r, authorization_request); + rv = HTTP_MOVED_TEMPORARILY; + +out: + + return rv; +} + /* * send an OpenID Connect authorization request to the specified provider */ @@ -233,6 +292,10 @@ int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *pro /* construct a HTML POST auto-submit page with the authorization request parameters */ rv = oidc_proto_html_post(r, oidc_cfg_provider_authorization_endpoint_url_get(provider), params); + } else if (oidc_cfg_provider_auth_request_method_get(provider) == OIDC_AUTH_REQUEST_METHOD_PAR) { + + rv = oidc_proto_pushed_authorization_request(r, provider, params); + } else if (oidc_cfg_provider_auth_request_method_get(provider) == OIDC_AUTH_REQUEST_METHOD_GET) { /* construct the full authorization request URL */