Skip to content

Commit

Permalink
add DPoP and FAPI 2.0 support
Browse files Browse the repository at this point in the history
- add (client) support for RFC 9449 OAuth 2.0 Demonstrating Proof of
Possession (DPoP)
- replace multi-provider .conf "issuer_specific_redirect_uri" boolean
with "response_require_iss" boolean
- tighten up the "aud" claim validation in ID tokens
- add support for the FAPI 2.0 Security Profile
https://openid.net/specs/fapi-2_0-security-profile-ID2.html

Signed-off-by: Hans Zandbelt <[email protected]>
  • Loading branch information
zandbelt committed Jun 4, 2024
1 parent edc5ef8 commit 488dadf
Show file tree
Hide file tree
Showing 23 changed files with 413 additions and 127 deletions.
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
06/04/2024
- add (client) support for RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)
TODO: must support server provided nonce
- replace multi-provider .conf "issuer_specific_redirect_uri" boolean with "response_require_iss" boolean
- tighten up the "aud" claim validation in ID tokens
- add support for the FAPI 2.0 Security Profile https://openid.net/specs/fapi-2_0-security-profile-ID2.html

05/30/2024
- add support for RFC 9126 OAuth 2.0 Pushed Authorization Requests

Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ libauth_openidc_la_SOURCES = \
src/handle/authz.c \
src/handle/content.c \
src/handle/discovery.c \
src/handle/dpop.c \
src/handle/info.c \
src/handle/jwks.c \
src/handle/logout.c \
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Interoperability
- [OpenID Connect Dynamic Client Registration 1.0](http://openid.net/specs/openid-connect-registration-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)
- [RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)](https://tools.ietf.org/html/rfc9449)
- [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-2_0-security-profile-ID2.html)
- [OAuth 2.0 Form Post Response Mode 1.0](http://openid.net/specs/oauth-v2-form-post-response-mode-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)
- [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)*
Expand Down
17 changes: 8 additions & 9 deletions src/cfg/provider.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ struct oidc_provider_t {
oidc_userinfo_token_method_t userinfo_token_method;
char *request_object;
oidc_auth_request_method_t auth_request_method;
int issuer_specific_redirect_uri;
int response_require_iss;
};

#define OIDC_PROVIDER_MEMBER_FUNCS_TYPE_DEF(member, type, def_val) \
Expand Down Expand Up @@ -297,10 +297,10 @@ OIDC_PROVIDER_MEMBER_FUNCS_FLAG(ssl_validate_server, OIDC_DEFAULT_SSL_VALIDATE_S
#define OIDC_DEFAULT_VALIDATE_ISSUER 1
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(validate_issuer, OIDC_DEFAULT_VALIDATE_ISSUER)

// define whether the issuer will be added to the redirect uri by default to mitigate the IDP mixup attack
// only used from metadata in multi-provider setups
#define OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI 0
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(issuer_specific_redirect_uri, OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI)
// define whether the iss parameter will be required in the response to the redirect uri by default to mitigate the IDP
// mixup attack only used from metadata in multi-provider setups
#define OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS 0
OIDC_PROVIDER_MEMBER_FUNCS_FLAG(response_require_iss, OIDC_DEFAULT_PROVIDER_RESPONSE_REQUIRE_ISS)
// only used from metadata in multi-provider setups
OIDC_PROVIDER_MEMBER_FUNCS_STR(registration_token, NULL)

Expand Down Expand Up @@ -635,7 +635,7 @@ static void oidc_cfg_provider_init(oidc_provider_t *provider) {
provider->userinfo_refresh_interval = OIDC_CONFIG_POS_INT_UNSET;
provider->request_object = NULL;

provider->issuer_specific_redirect_uri = OIDC_CONFIG_POS_INT_UNSET;
provider->response_require_iss = OIDC_CONFIG_POS_INT_UNSET;
}

void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_provider_t *base,
Expand Down Expand Up @@ -744,9 +744,8 @@ void oidc_cfg_provider_merge(apr_pool_t *pool, oidc_provider_t *dst, const oidc_
: base->userinfo_refresh_interval;
dst->request_object = add->request_object != NULL ? add->request_object : base->request_object;

dst->issuer_specific_redirect_uri = add->issuer_specific_redirect_uri != OIDC_CONFIG_POS_INT_UNSET
? add->issuer_specific_redirect_uri
: base->issuer_specific_redirect_uri;
dst->response_require_iss = add->response_require_iss != OIDC_CONFIG_POS_INT_UNSET ? add->response_require_iss
: base->response_require_iss;
}

oidc_provider_t *oidc_cfg_provider_create(apr_pool_t *pool) {
Expand Down
2 changes: 1 addition & 1 deletion src/cfg/provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(ssl_validate_server)
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(validate_issuer)
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(idtoken_iat_slack)
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(session_max_duration)
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(issuer_specific_redirect_uri)
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(response_require_iss)
// ints with 2 args
OIDC_CFG_PROVIDER_MEMBER_FUNCS_INT_DECL(userinfo_refresh_interval, const char *)

Expand Down
17 changes: 17 additions & 0 deletions src/handle/content.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ int oidc_content_handler(request_rec *r) {
/* free resources allocated for the session */
oidc_session_free(r, session);

} else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_DPOP)) {

OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_DPOP);

/* see if a session was retained in the request state */
apr_pool_userdata_get((void **)&session, OIDC_USERDATA_SESSION, r->pool);

/* if no retained session was found, load it from the cache or create a new one*/
if (session != NULL)
/* handle request to create a DPoP proof */
rc = oidc_dpop_request(r, c, session);
else
rc = HTTP_UNAUTHORIZED;

/* free resources allocated for the session */
oidc_session_free(r, session);

} else if (oidc_util_request_has_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS)) {

OIDC_METRICS_COUNTER_INC(r, c, OM_CONTENT_REQUEST_JWKS);
Expand Down
138 changes: 138 additions & 0 deletions src/handle/dpop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/***************************************************************************
* Copyright (C) 2017-2024 ZmartZone Holding BV
* All rights reserved.
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - [email protected]
*/

#include "handle/handle.h"
#include "mod_auth_openidc.h"
#include "proto.h"
#include "util.h"

#define OIDC_DPOP_PARAM_URL "url"
#define OIDC_DPOP_PARAM_METHOD "method"

int oidc_dpop_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) {
int rc = HTTP_BAD_REQUEST;
char *s_url = NULL;
char *s_access_token = NULL;
const char *session_access_token = NULL;
char *s_method = NULL;
char *s_dpop = NULL;
char *s_response = NULL;
json_t *json = NULL;

/* try to make sure that the proof-of-possession semantics are preserved */
if ((_oidc_strnatcasecmp(r->useragent_ip, r->connection->local_ip) != 0) &&
(apr_table_get(r->subprocess_env, "OIDC_DPOP_API_INSECURE") == 0)) {
oidc_warn(
r,
"reject DPoP creation request from remote host: you should create a separate virtual (sub)host "
"that requires client certificate authentication to allow and proxy this request "
"(r->useragent_ip=%s, "
"r->connection->local_ip=%s)",
r->useragent_ip, r->connection->local_ip);
rc = HTTP_UNAUTHORIZED;
goto end;
}

/* retrieve the access token parameter */
oidc_util_request_parameter_get(r, OIDC_REDIRECT_URI_REQUEST_DPOP, &s_access_token);
if (s_access_token == NULL) {
oidc_error(r, "\"access_token\" value to the \"%s\" parameter is missing",
OIDC_REDIRECT_URI_REQUEST_DPOP);
goto end;
}

/* retrieve the URL parameter */
oidc_util_request_parameter_get(r, OIDC_DPOP_PARAM_URL, &s_url);
if (s_url == NULL) {
oidc_error(r, "\"url\" parameter is missing");
goto end;
}

/* parse the optional HTTP method parameter */
oidc_util_request_parameter_get(r, OIDC_DPOP_PARAM_METHOD, &s_method);
if (_oidc_strnatcasecmp(s_method, "post") == 0)
s_method = "POST";
else if ((_oidc_strnatcasecmp(s_method, "get") == 0) || (s_method == NULL))
s_method = "GET";

/* check that we actually have a user session and this is someone calling with a proper session cookie */
if (session->remote_user == NULL) {
oidc_warn(r, "no user session found");
rc = HTTP_UNAUTHORIZED;
goto end;
}

session_access_token = oidc_session_get_access_token(r, session);
if (session_access_token == NULL) {
oidc_error(r, "no \"access_token\" was found in the session");
goto end;
}

if (_oidc_strcmp(s_access_token, session_access_token) != 0) {
oidc_error(r, "the provided \"access_token\" parameter is not matching the current access token stored "
"in the user session");
goto end;
}

/* create the DPoP header value */
s_dpop = oidc_proto_dpop(r, c, s_url, s_method, s_access_token);
if (s_dpop == NULL) {
oidc_error(r, "creating the DPoP proof value failed");
rc = HTTP_INTERNAL_SERVER_ERROR;
goto end;
}

/* assemble and serialize the JSON response object */
json = json_object();
json_object_set_new(json, OIDC_HTTP_HDR_DPOP, json_string(s_dpop));
s_response = oidc_util_encode_json_object(r, json, JSON_COMPACT | JSON_PRESERVE_ORDER);

/* return the serialized JSON response */
rc = oidc_util_http_send(r, s_response, _oidc_strlen(s_response), OIDC_HTTP_CONTENT_TYPE_JSON, OK);

end:

if (json)
json_decref(json);

return rc;
}
3 changes: 3 additions & 0 deletions src/handle/handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ int oidc_discovery_request(request_rec *r, oidc_cfg_t *cfg);
apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg_t *cfg);
int oidc_discovery_response(request_rec *r, oidc_cfg_t *c);

// dpop.c
int oidc_dpop_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);

// info.c
int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, apr_byte_t needs_save);

Expand Down
14 changes: 8 additions & 6 deletions src/handle/logout.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ static void oidc_logout_revoke_tokens(request_rec *r, oidc_cfg_t *c, oidc_sessio
apr_table_setn(params, OIDC_PROTO_TOKEN, token);

if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth,
bearer_auth, oidc_cfg_provider_ssl_validate_server_get(provider), &response,
NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c),
oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) {
bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider),
&response, NULL, oidc_cfg_http_timeout_long_get(c),
oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL,
NULL) == FALSE) {
oidc_warn(r, "revoking refresh token failed");
}
apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE_HINT);
Expand All @@ -108,9 +109,10 @@ static void oidc_logout_revoke_tokens(request_rec *r, oidc_cfg_t *c, oidc_sessio
apr_table_setn(params, OIDC_PROTO_TOKEN, token);

if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth,
bearer_auth, oidc_cfg_provider_ssl_validate_server_get(provider), &response,
NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c),
oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) {
bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider),
&response, NULL, oidc_cfg_http_timeout_long_get(c),
oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL,
NULL) == FALSE) {
oidc_warn(r, "revoking access token failed");
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/handle/request.c
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,8 @@ int oidc_request_authenticate_user(request_rec *r, oidc_cfg_t *c, oidc_provider_

/* send off to the OpenID Connect Provider */
// TODO: maybe show intermediate/progress screen "redirecting to"
rc = oidc_proto_authorization_request(r, provider, login_hint, oidc_util_redirect_uri_iss(r, c, provider),
state, proto_state, id_token_hint, code_challenge, auth_request_params,
path_scope);
rc = oidc_proto_authorization_request(r, provider, login_hint, oidc_util_redirect_uri(r, c), state, proto_state,
id_token_hint, code_challenge, auth_request_params, path_scope);

OIDC_METRICS_TIMING_ADD(r, c, OM_AUTHN_REQUEST);

Expand Down
3 changes: 1 addition & 2 deletions src/handle/session_management.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ int oidc_session_management(request_rec *r, oidc_cfg_t *c, oidc_session_t *sessi
* session now?
*/
return oidc_request_authenticate_user(
r, c, provider,
apr_psprintf(r->pool, "%s?session=iframe_rp", oidc_util_redirect_uri_iss(r, c, provider)), NULL,
r, c, provider, apr_psprintf(r->pool, "%s?session=iframe_rp", oidc_util_redirect_uri(r, c)), NULL,
id_token_hint, "none", oidc_cfg_dir_path_auth_request_params_get(r),
oidc_cfg_dir_path_scope_get(r));
}
Expand Down
Loading

0 comments on commit 488dadf

Please sign in to comment.