From bdb1e2bf80130c566c9dcf7323d6f9f61973f5da Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Fri, 7 Jun 2024 21:08:23 +0200 Subject: [PATCH] support DPoP nonces to the token endpoint return response headers from outgoing HTTP requests to callers Signed-off-by: Hans Zandbelt --- ChangeLog | 4 ++ src/handle/dpop.c | 2 +- src/handle/logout.c | 4 +- src/http.c | 128 +++++++++++++++++++++++++++++++----------- src/http.h | 8 ++- src/metadata.c | 6 +- src/oauth.c | 6 +- src/proto/discovery.c | 2 +- src/proto/dpop.c | 7 ++- src/proto/proto.h | 3 +- src/proto/request.c | 2 +- src/proto/token.c | 75 ++++++++++++++++++++----- src/proto/userinfo.c | 10 ++-- 13 files changed, 190 insertions(+), 67 deletions(-) diff --git a/ChangeLog b/ChangeLog index 61aa18f7..ff4406aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +06/07/2024 +- return response headers from outgoing HTTP requests to callers +- support DPoP nonces to the token endpoint + 06/06/2024 - add OIDCDPoPMode [off|optional|required] primitive - store the token_type in the session diff --git a/src/handle/dpop.c b/src/handle/dpop.c index 4a634c32..9c103399 100644 --- a/src/handle/dpop.c +++ b/src/handle/dpop.c @@ -114,7 +114,7 @@ int oidc_dpop_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session) { } /* create the DPoP header value */ - s_dpop = oidc_proto_dpop_create(r, c, s_url, s_method, s_access_token); + s_dpop = oidc_proto_dpop_create(r, c, s_url, s_method, s_access_token, NULL); if (s_dpop == NULL) { oidc_error(r, "creating the DPoP proof value failed"); rc = HTTP_INTERNAL_SERVER_ERROR; diff --git a/src/handle/logout.c b/src/handle/logout.c index c9613382..6bda68d3 100644 --- a/src/handle/logout.c +++ b/src/handle/logout.c @@ -94,7 +94,7 @@ static void oidc_logout_revoke_tokens(request_rec *r, oidc_cfg_t *c, oidc_sessio if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth, bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider), - &response, NULL, oidc_cfg_http_timeout_long_get(c), + &response, NULL, 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"); @@ -110,7 +110,7 @@ static void oidc_logout_revoke_tokens(request_rec *r, oidc_cfg_t *c, oidc_sessio if (oidc_http_post_form(r, oidc_cfg_provider_revocation_endpoint_url_get(provider), params, basic_auth, bearer_auth, NULL, oidc_cfg_provider_ssl_validate_server_get(provider), - &response, NULL, oidc_cfg_http_timeout_long_get(c), + &response, NULL, 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"); diff --git a/src/http.c b/src/http.c index 497daf5b..eeadfede 100644 --- a/src/http.c +++ b/src/http.c @@ -416,28 +416,28 @@ char *oidc_http_hdr_normalize_name(const request_rec *r, const char *str) { } /* buffer to hold HTTP call responses */ -typedef struct oidc_curl_buffer { +typedef struct oidc_curl_resp_data_ctx_t { request_rec *r; char *memory; size_t size; -} oidc_curl_buffer; +} oidc_curl_resp_data_ctx_t; /* maximum acceptable size of HTTP responses: 10 Mb */ -#define OIDC_CURL_MAX_RESPONSE_SIZE 1024 * 1024 * 10 +#define OIDC_CURL_RESPONSE_DATA_SIZE_MAX 1024 * 1024 * 10 /* * callback for CURL to write bytes that come back from an HTTP call */ -size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) { +static size_t oidc_http_response_data(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; - oidc_curl_buffer *mem = (oidc_curl_buffer *)userp; + oidc_curl_resp_data_ctx_t *mem = (oidc_curl_resp_data_ctx_t *)userp; /* check if we don't run over the maximum buffer/memory size for HTTP responses */ - if (mem->size + realsize > OIDC_CURL_MAX_RESPONSE_SIZE) { + if (mem->size + realsize > OIDC_CURL_RESPONSE_DATA_SIZE_MAX) { oidc_error( mem->r, "HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d", - (long)mem->size, (long)realsize, OIDC_CURL_MAX_RESPONSE_SIZE); + (long)mem->size, (long)realsize, OIDC_CURL_RESPONSE_DATA_SIZE_MAX); return 0; } @@ -459,6 +459,67 @@ size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) { return realsize; } +/* buffer to hold HTTP response headers */ +typedef struct oidc_curl_resp_hdr_ctx_t { + request_rec *r; + apr_hash_t *hdrs; +} oidc_curl_resp_hdr_ctx_t; + +/* + * callback for CURL to write response headers that come back from an HTTP call + */ +static size_t oidc_http_response_header(char *buffer, size_t size, size_t nitems, void *userdata) { + /* received header is nitems * size long in 'buffer' NOT ZERO TERMINATED */ + oidc_curl_resp_hdr_ctx_t *ctx = (oidc_curl_resp_hdr_ctx_t *)userdata; + char *hdr = NULL, *value = NULL, *h_name = NULL; + apr_hash_index_t *hi = NULL; + apr_ssize_t h_len = 0; + int i = 0; + + /* see if there is a header to search for */ + if ((ctx->hdrs == NULL) || (apr_hash_count(ctx->hdrs) == 0)) + goto end; + + /* make hdr a \0 terminated string for easier processing */ + hdr = apr_pstrndup(ctx->r->pool, buffer, nitems * size); + + /* search for a name: value pair */ + value = _oidc_strstr(hdr, OIDC_STR_COLON); + if (value == NULL) + goto end; + + /* split the header name and value */ + *value = '\0'; + + /* see if there's any header value characters at all after the colon */ + if (_oidc_strlen(hdr) < nitems * size) { + value++; + /* skip spaces after the colon */ + while (*value == ' ') + value++; + /* remove trailing /r/n */ + i = strlen(value) - 1; + while ((value[i] == '\r') || (value[i] == '\n')) + value[i--] = '\0'; + } + + // TODO: would be faster to use all lowercase keys + + /* check if the caller is interested in the value of the current response header */ + for (hi = apr_hash_first(NULL, ctx->hdrs); hi; hi = apr_hash_next(hi)) { + apr_hash_this(hi, (const void **)&h_name, &h_len, NULL); + if (_oidc_strnatcasecmp(hdr, h_name) == 0) { + oidc_debug(ctx->r, "returning response header: %s: %s", h_name, value); + apr_hash_set(ctx->hdrs, h_name, APR_HASH_KEY_STRING, apr_pstrdup(ctx->r->pool, value)); + break; + } + } + +end: + + return nitems * size; +} + /* context structure for encoding parameters */ typedef struct oidc_http_encode_t { request_rec *r; @@ -603,12 +664,14 @@ static const char *oidc_http_user_agent(request_rec *r) { static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char *data, const char *content_type, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, long *response_code, - oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, + apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, + const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { - char curlError[CURL_ERROR_SIZE]; - oidc_curl_buffer curlBuffer; + char curl_err[CURL_ERROR_SIZE]; + oidc_curl_resp_data_ctx_t d_buf = {r, NULL, 0}; + oidc_curl_resp_hdr_ctx_t h_buf = {r, response_hdrs}; CURL *curl = NULL; struct curl_slist *h_list = NULL; int i = 0; @@ -635,13 +698,13 @@ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char } /* set the error buffer as empty before performing a request */ - curlError[0] = 0; + curl_err[0] = 0; /* some of these are not really required */ curl_easy_setopt(curl, CURLOPT_HEADER, 0L); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_err); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); @@ -649,12 +712,13 @@ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char curl_easy_setopt(curl, CURLOPT_TIMEOUT, http_timeout->request_timeout); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, http_timeout->connect_timeout); - /* setup the buffer where the response will be written to */ - curlBuffer.r = r; - curlBuffer.memory = NULL; - curlBuffer.size = 0; - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&curlBuffer); + /* setup the buffer where the response data will be written to */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_http_response_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&d_buf); + + /* setup the buffer where the response headers will be written to */ + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, oidc_http_response_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&h_buf); #ifndef LIBCURL_NO_CURLPROTO #if LIBCURL_VERSION_NUM >= 0x075500 @@ -788,14 +852,14 @@ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char if (res == CURLE_OPERATION_TIMEDOUT) { /* in case of a request/transfer timeout (which includes the connect timeout) we'll not retry */ oidc_error(r, "curl_easy_perform failed with a timeout for %s: [%s]; won't retry", url, - curlError[0] ? curlError : ""); + curl_err[0] ? curl_err : ""); OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, - curlError[0] ? curlError : "timeout") + curl_err[0] ? curl_err : "timeout") break; } oidc_error(r, "curl_easy_perform(%d/%d) failed for %s with: [%s]", i + 1, http_timeout->retries + 1, - url, curlError[0] ? curlError : ""); - OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, curlError[0] ? curlError : "undefined") + url, curl_err[0] ? curl_err : ""); + OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, curl_err[0] ? curl_err : "undefined") /* in case of a connectivity/network glitch we'll back off before retrying */ if (i < http_timeout->retries) apr_sleep(apr_time_from_msec(http_timeout->retry_interval)); @@ -808,7 +872,7 @@ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_HTTP_RESPONSE_CODE, apr_psprintf(r->pool, "%ld", http_code)); - *response = apr_pstrmemdup(r->pool, curlBuffer.memory, curlBuffer.size); + *response = apr_pstrmemdup(r->pool, d_buf.memory, d_buf.size); if (response_code) *response_code = http_code; @@ -831,13 +895,13 @@ static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char */ apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *query_url = oidc_http_query_encoded_url(r, url, params); return oidc_http_request(r, query_url, NULL, NULL, basic_auth, access_token, dpop, ssl_validate_server, - response, response_code, http_timeout, outgoing_proxy, pass_cookies, ssl_cert, ssl_key, - ssl_key_pwd); + response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies, + ssl_cert, ssl_key, ssl_key_pwd); } /* @@ -845,13 +909,13 @@ apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *par */ apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *data = oidc_http_form_encoded_data(r, params); return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED, basic_auth, access_token, dpop, - ssl_validate_server, response, response_code, http_timeout, outgoing_proxy, - pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); + ssl_validate_server, response, response_code, response_hdrs, http_timeout, + outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); } /* @@ -859,13 +923,13 @@ apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_ */ apr_byte_t oidc_http_post_json(request_rec *r, const char *url, json_t *json, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { char *data = json != NULL ? oidc_util_encode_json_object(r, json, JSON_COMPACT) : NULL; return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_JSON, basic_auth, access_token, dpop, - ssl_validate_server, response, response_code, http_timeout, outgoing_proxy, - pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); + ssl_validate_server, response, response_code, response_hdrs, http_timeout, + outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); } /* diff --git a/src/http.h b/src/http.h index 32b17fb4..d1fdff3f 100644 --- a/src/http.h +++ b/src/http.h @@ -45,6 +45,7 @@ #define _MOD_AUTH_OPENIDC_HTTP_H_ #include +#include #include // clang-format off #include @@ -87,6 +88,7 @@ #define OIDC_HTTP_HDR_WWW_AUTHENTICATE "WWW-Authenticate" #define OIDC_HTTP_HDR_TRACE_PARENT "traceparent" #define OIDC_HTTP_HDR_DPOP "DPoP" +#define OIDC_HTTP_HDR_DPOP_NONCE "DPoP-Nonce" #define OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST "XMLHttpRequest" #define OIDC_HTTP_HDR_VAL_NAVIGATE "navigate" @@ -141,17 +143,17 @@ const char *oidc_http_hdr_forwarded_get(const request_rec *r, const char *elem); char *oidc_http_hdr_normalize_name(const request_rec *r, const char *str); apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_http_post_json(request_rec *r, const char *url, json_t *data, const char *basic_auth, const char *access_token, const char *dpop, int ssl_validate_server, char **response, - long *response_code, oidc_http_timeout_t *http_timeout, + long *response_code, apr_hash_t *response_hdrs, oidc_http_timeout_t *http_timeout, const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd); apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char *param); diff --git a/src/metadata.c b/src/metadata.c index aa6969cc..ce69c733 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -595,7 +595,7 @@ static apr_byte_t oidc_metadata_client_register(request_rec *r, oidc_cfg_t *cfg, /* dynamically register the client with the specified parameters */ if (oidc_http_post_json(r, oidc_cfg_provider_registration_endpoint_url_get(provider), data, NULL, oidc_cfg_provider_registration_token_get(provider), NULL, - oidc_cfg_provider_ssl_validate_server_get(provider), response, NULL, + oidc_cfg_provider_ssl_validate_server_get(provider), response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { json_decref(data); @@ -623,7 +623,7 @@ static apr_byte_t oidc_metadata_jwks_retrieve_and_cache(request_rec *r, oidc_cfg const char *url = (jwks_uri->signed_uri != NULL) ? jwks_uri->signed_uri : jwks_uri->uri; /* get the JWKs from the specified URL with the specified parameters */ - if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, ssl_validate_server, &response, NULL, + if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, ssl_validate_server, &response, NULL, 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) return FALSE; @@ -734,7 +734,7 @@ apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg_t *cfg, cons /* get provider metadata from the specified URL with the specified parameters */ if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, - oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), response, NULL, + oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_METADATA_ERROR); diff --git a/src/oauth.c b/src/oauth.c index a811cc1c..b25d2e41 100644 --- a/src/oauth.c +++ b/src/oauth.c @@ -61,7 +61,7 @@ apr_byte_t oidc_oauth_metadata_provider_retrieve(request_rec *r, oidc_cfg_t *cfg /* get provider metadata from the specified URL with the specified parameters */ if (oidc_http_get(r, url, NULL, NULL, NULL, NULL, oidc_cfg_oauth_ssl_validate_server_get(cfg), response, NULL, - oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), + NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) return FALSE; @@ -172,7 +172,7 @@ static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg_t *c /* call the endpoint with the constructed parameter set and return the resulting response */ return oidc_cfg_oauth_introspection_endpoint_method_get(c) == OIDC_INTROSPECTION_METHOD_GET ? oidc_http_get(r, oidc_cfg_oauth_introspection_endpoint_url_get(c), params, basic_auth, bearer_auth, - NULL, oidc_cfg_oauth_ssl_validate_server_get(c), response, NULL, + NULL, oidc_cfg_oauth_ssl_validate_server_get(c), response, NULL, NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_oauth_introspection_endpoint_tls_client_cert_get(c), @@ -180,7 +180,7 @@ static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg_t *c oidc_cfg_oauth_introspection_endpoint_tls_client_key_pwd_get(c)) : oidc_http_post_form(r, oidc_cfg_oauth_introspection_endpoint_url_get(c), params, basic_auth, bearer_auth, NULL, oidc_cfg_oauth_ssl_validate_server_get(c), response, NULL, - oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), + NULL, oidc_cfg_http_timeout_long_get(c), oidc_cfg_outgoing_proxy_get(c), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_oauth_introspection_endpoint_tls_client_cert_get(c), oidc_cfg_oauth_introspection_endpoint_tls_client_key_get(c), diff --git a/src/proto/discovery.c b/src/proto/discovery.c index 5eb0165e..99631578 100644 --- a/src/proto/discovery.c +++ b/src/proto/discovery.c @@ -61,7 +61,7 @@ static apr_byte_t oidc_proto_webfinger_discovery(request_rec *r, oidc_cfg_t *cfg char *response = NULL; if (oidc_http_get(r, url, params, NULL, NULL, NULL, - oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), &response, NULL, + oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), &response, NULL, NULL, oidc_cfg_http_timeout_short_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL) == FALSE) { /* errors will have been logged by now */ diff --git a/src/proto/dpop.c b/src/proto/dpop.c index 47301c90..6571b8ea 100644 --- a/src/proto/dpop.c +++ b/src/proto/dpop.c @@ -50,7 +50,7 @@ * generate a DPoP proof for the specified URL/method/access_token */ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method, - const char *access_token) { + const char *access_token, const char *nonce) { // TODO: share with create_userinfo_jwt oidc_jwt_t *jwt = NULL; oidc_jwk_t *jwk = NULL; @@ -76,7 +76,7 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_HTU, json_string(url)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT, json_integer(apr_time_sec(apr_time_now()))); - if (access_token) { + if (access_token != NULL) { if (oidc_jose_hash_and_base64url_encode(r->pool, OIDC_JOSE_ALG_SHA256, access_token, strlen(access_token), &ath, &err) == FALSE) { oidc_error(r, "oidc_jose_hash_and_base64url_encode failed: %s", oidc_jose_e2s(r->pool, err)); @@ -85,6 +85,9 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ATH, json_string(ath)); } + if (nonce != NULL) + json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_NONCE, json_string(nonce)); + if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, &cser) == FALSE) goto end; diff --git a/src/proto/proto.h b/src/proto/proto.h index 4af33cce..2d514b9a 100644 --- a/src/proto/proto.h +++ b/src/proto/proto.h @@ -114,6 +114,7 @@ #define OIDC_PROTO_BEARER "Bearer" #define OIDC_PROTO_BASIC "Basic" #define OIDC_PROTO_DPOP "DPoP" +#define OIDC_PROTO_DPOP_USE_NONCE "use_dpop_nonce" /* nonce bytes length */ #define OIDC_PROTO_NONCE_LENGTH 32 @@ -133,7 +134,7 @@ apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const // dpop.c char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method, - const char *access_token); + const char *access_token, const char *nonce); // id_token.c apr_byte_t oidc_proto_idtoken_parse(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *id_token, diff --git a/src/proto/request.c b/src/proto/request.c index b32ceff7..08ec8c34 100644 --- a/src/proto/request.c +++ b/src/proto/request.c @@ -104,7 +104,7 @@ static int oidc_proto_request_auth_push(request_rec *r, struct oidc_provider_t * goto out; if (oidc_http_post_form(r, endpoint_url, params, basic_auth, bearer_auth, NULL, - oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, + oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, 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; diff --git a/src/proto/token.c b/src/proto/token.c index b579ba49..2a422387 100644 --- a/src/proto/token.c +++ b/src/proto/token.c @@ -69,42 +69,86 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi apr_table_t *params, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token) { + apr_byte_t rv = FALSE; char *response = NULL; char *basic_auth = NULL; char *bearer_auth = NULL; char *dpop = NULL; - json_t *j_result = NULL, *j_expires_in = NULL; + char *dpop_nonce = NULL; + apr_hash_t *response_hdrs = NULL; + json_t *j_result = NULL, *j_expires_in = NULL, *j_error = NULL; /* add the token endpoint authentication credentials */ 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) - return FALSE; + goto end; /* add any configured extra static parameters to the token endpoint */ oidc_util_table_add_query_encoded_params(r->pool, params, oidc_cfg_provider_token_endpoint_params_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); + if (oidc_cfg_provider_dpop_mode_get(provider) != OIDC_DPOP_MODE_OFF) { + + response_hdrs = apr_hash_make(r->pool); + apr_hash_set(response_hdrs, OIDC_HTTP_HDR_AUTHORIZATION, APR_HASH_KEY_STRING, ""); + apr_hash_set(response_hdrs, OIDC_HTTP_HDR_DPOP_NONCE, APR_HASH_KEY_STRING, ""); + apr_hash_set(response_hdrs, OIDC_HTTP_HDR_CONTENT_TYPE, APR_HASH_KEY_STRING, ""); + + dpop = oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL, + NULL); + } /* send the request to the token endpoint */ if (oidc_http_post_form(r, oidc_cfg_provider_token_endpoint_url_get(provider), params, basic_auth, bearer_auth, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), &response, NULL, - oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), + response_hdrs, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider), oidc_cfg_provider_token_endpoint_tls_client_key_get(provider), oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)) == FALSE) { - oidc_warn(r, "error when calling the token endpoint (%s)", - oidc_cfg_provider_token_endpoint_url_get(provider)); - return FALSE; + oidc_error(r, "error when calling the token endpoint (%s)", + oidc_cfg_provider_token_endpoint_url_get(provider)); + goto end; } /* check for errors, the response itself will have been logged already */ - if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) - return FALSE; + if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) { + + j_error = json_object_get(j_result, OIDC_PROTO_ERROR); + if ((j_error == NULL) || (!json_is_string(j_error)) || + (_oidc_strcmp(json_string_value(j_error), OIDC_PROTO_DPOP_USE_NONCE) != 0)) + goto end; + + json_decref(j_result); + + /* try again with a DPoP nonce provided by the server */ + dpop_nonce = (char *)apr_hash_get(response_hdrs, OIDC_HTTP_HDR_DPOP_NONCE, APR_HASH_KEY_STRING); + if (dpop_nonce == NULL) { + oidc_error(r, "error is \"%s\" but no \"%s\" header found", OIDC_PROTO_DPOP_USE_NONCE, + OIDC_HTTP_HDR_DPOP_NONCE); + goto end; + } + + dpop = oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL, + dpop_nonce); + + if (oidc_http_post_form(r, oidc_cfg_provider_token_endpoint_url_get(provider), params, basic_auth, + bearer_auth, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), + &response, NULL, NULL, oidc_cfg_http_timeout_long_get(cfg), + oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), + oidc_cfg_provider_token_endpoint_tls_client_cert_get(provider), + oidc_cfg_provider_token_endpoint_tls_client_key_get(provider), + oidc_cfg_provider_token_endpoint_tls_client_key_pwd_get(provider)) == FALSE) { + oidc_error(r, "error when calling the token endpoint (%s)", + oidc_cfg_provider_token_endpoint_url_get(provider)); + goto end; + } + + if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE) + goto end; + } /* get the id_token from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_ID_TOKEN, id_token, NULL); @@ -119,7 +163,7 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi 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; + goto end; } /* check the new token type */ @@ -145,9 +189,14 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi /* get the refresh_token from the parsed response */ oidc_util_json_object_get_string(r->pool, j_result, OIDC_PROTO_REFRESH_TOKEN, refresh_token, NULL); - json_decref(j_result); + rv = TRUE; - return TRUE; +end: + + if (j_result) + json_decref(j_result); + + return rv; } /* diff --git a/src/proto/userinfo.c b/src/proto/userinfo.c index 952b8646..702734ca 100644 --- a/src/proto/userinfo.c +++ b/src/proto/userinfo.c @@ -183,7 +183,7 @@ static apr_byte_t oidc_proto_userinfo_request_composite_claims(request_rec *r, o oidc_http_get( r, endpoint, NULL, NULL, access_token, NULL, oidc_cfg_provider_ssl_validate_server_get(oidc_cfg_provider_get(cfg)), - &s_json, NULL, oidc_cfg_http_timeout_long_get(cfg), + &s_json, NULL, NULL, oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), oidc_cfg_dir_pass_cookies_get(r), NULL, NULL, NULL); } @@ -253,10 +253,10 @@ apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_pro if (oidc_cfg_provider_userinfo_token_method_get(provider) == OIDC_USER_INFO_TOKEN_METHOD_HEADER) { 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); + "GET", access_token, NULL); if (oidc_http_get(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), NULL, NULL, access_token, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), response, response_code, - oidc_cfg_http_timeout_long_get(cfg), oidc_cfg_outgoing_proxy_get(cfg), + 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) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_USERINFO_ERROR); return FALSE; @@ -266,10 +266,10 @@ apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_pro apr_table_setn(params, OIDC_PROTO_ACCESS_TOKEN, access_token); 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); + "POST", access_token, NULL); if (oidc_http_post_form(r, oidc_cfg_provider_userinfo_endpoint_url_get(provider), params, NULL, NULL, dpop, oidc_cfg_provider_ssl_validate_server_get(provider), response, - response_code, oidc_cfg_http_timeout_long_get(cfg), + response_code, 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) { OIDC_METRICS_COUNTER_INC(r, cfg, OM_PROVIDER_USERINFO_ERROR);