Skip to content

Commit

Permalink
support DPoP nonces to the token endpoint
Browse files Browse the repository at this point in the history
return response headers from outgoing HTTP requests to callers

Signed-off-by: Hans Zandbelt <[email protected]>
  • Loading branch information
zandbelt committed Jun 7, 2024
1 parent c70a8a3 commit bdb1e2b
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 67 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/handle/dpop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/handle/logout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down
128 changes: 96 additions & 32 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -635,26 +698,27 @@ 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);

/* set the timeouts */
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
Expand Down Expand Up @@ -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 : "<n/a>");
curl_err[0] ? curl_err : "<n/a>");
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 : "<n/a>");
OIDC_METRICS_COUNTER_INC_SPEC(r, c, OM_PROVIDER_CONNECT_ERROR, curlError[0] ? curlError : "undefined")
url, curl_err[0] ? curl_err : "<n/a>");
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));
Expand All @@ -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;

Expand All @@ -831,41 +895,41 @@ 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);
}

/*
* execute HTTP POST request with form-encoded data
*/
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);
}

/*
* execute HTTP POST request with JSON-encoded data
*/
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);
}

/*
Expand Down
8 changes: 5 additions & 3 deletions src/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#define _MOD_AUTH_OPENIDC_HTTP_H_

#include <apr.h>
#include <apr_hash.h>
#include <apr_time.h>
// clang-format off
#include <httpd.h>
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/metadata.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/oauth.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -172,15 +172,15 @@ 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),
oidc_cfg_oauth_introspection_endpoint_tls_client_key_get(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),
Expand Down
2 changes: 1 addition & 1 deletion src/proto/discovery.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading

0 comments on commit bdb1e2b

Please sign in to comment.