Skip to content

Commit

Permalink
support DPoP nonces to the userinfo endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: Hans Zandbelt <[email protected]>
  • Loading branch information
zandbelt committed Jun 8, 2024
1 parent 191b069 commit 336ba21
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 121 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
08/08/2024
- support DPoP nonces to the userinfo endpoint

06/07/2024
- return response headers from outgoing HTTP requests to callers
- support DPoP nonces to the token endpoint
Expand Down
4 changes: 2 additions & 2 deletions src/handle/dpop.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ 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_nonce);
if (s_dpop == NULL) {
if ((oidc_proto_dpop_create(r, c, s_url, s_method, s_access_token, s_nonce, &s_dpop) == FALSE) ||
(s_dpop == NULL)) {
oidc_error(r, "creating the DPoP proof value failed");
rc = HTTP_INTERNAL_SERVER_ERROR;
goto end;
Expand Down
38 changes: 32 additions & 6 deletions src/proto/dpop.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,43 @@

#define OIDC_PROTO_DPOP_JWT_TYP "dpop+jwt"

apr_byte_t oidc_proto_dpop_use_nonce(request_rec *r, oidc_cfg_t *cfg, json_t *j_result, apr_hash_t *response_hdrs,
const char *url, const char *method, const char *access_token, char **dpop) {
apr_byte_t rv = FALSE;
char *dpop_nonce = NULL;

json_t *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;

/* 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;
}

rv = oidc_proto_dpop_create(r, cfg, url, method, access_token, dpop_nonce, dpop);

end:

return rv;
}

/*
* 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 *nonce) {
// TODO: share with create_userinfo_jwt
apr_byte_t oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method,
const char *access_token, const char *nonce, char **dpop) {
apr_byte_t rv = FALSE;
oidc_jwt_t *jwt = NULL;
oidc_jwk_t *jwk = NULL;
oidc_jose_error_t err;
char *jti = NULL;
cjose_err cjose_err;
char *s_jwk = NULL;
char *cser = NULL;
char *ath = NULL;

oidc_debug(r, "enter");
Expand Down Expand Up @@ -88,9 +112,11 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c
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)
if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, dpop) == FALSE)
goto end;

rv = TRUE;

end:

if (s_jwk)
Expand All @@ -99,5 +125,5 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c
if (jwt)
oidc_jwt_destroy(jwt);

return cser;
return rv;
}
6 changes: 4 additions & 2 deletions src/proto/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,10 @@ apr_byte_t oidc_proto_discovery_account_based(request_rec *r, oidc_cfg_t *cfg, c
apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const char *url, char **issuer);

// 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 *nonce);
apr_byte_t oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method,
const char *access_token, const char *nonce, char **dpop);
apr_byte_t oidc_proto_dpop_use_nonce(request_rec *r, oidc_cfg_t *cfg, json_t *j_result, apr_hash_t *response_hdrs,
const char *url, const char *method, const char *access_token, char **dpop);

// 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,
Expand Down
74 changes: 34 additions & 40 deletions src/proto/token.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ static apr_byte_t oidc_proto_validate_token_type(request_rec *r, oidc_provider_t
return TRUE;
}

/*
* send the request to the token endpoint
*/
static apr_byte_t oidc_proto_token_endpoint_call(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider,
apr_table_t *params, const char *basic_auth, const char *bearer_auth,
const char *dpop, char **response, apr_hash_t *response_hdrs) {
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,
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_error(r, "error when calling the token endpoint (%s)",
oidc_cfg_provider_token_endpoint_url_get(provider));
return FALSE;
}
return TRUE;
}

/*
* send a code/refresh request to the token endpoint and return the parsed contents
*/
Expand All @@ -70,13 +90,12 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi
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 *response = NULL;
char *dpop = NULL;
char *dpop_nonce = NULL;
apr_hash_t *response_hdrs = NULL;
json_t *j_result = NULL, *j_expires_in = NULL, *j_error = NULL;
json_t *j_result = NULL, *j_expires_in = NULL;

/* add the token endpoint authentication credentials */
if (oidc_proto_token_endpoint_auth(
Expand All @@ -96,55 +115,30 @@ apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oi
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);
if ((oidc_proto_dpop_create(r, cfg, oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL,
NULL, &dpop) == FALSE) &&
(oidc_cfg_provider_dpop_mode_get(provider) == OIDC_DPOP_MODE_REQUIRED))
goto end;
}

/* 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,
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_error(r, "error when calling the token endpoint (%s)",
oidc_cfg_provider_token_endpoint_url_get(provider));
if (oidc_proto_token_endpoint_call(r, cfg, provider, params, basic_auth, bearer_auth, dpop, &response,
response_hdrs) == FALSE)
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) {

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))
if (oidc_proto_dpop_use_nonce(r, cfg, j_result, response_hdrs,
oidc_cfg_provider_token_endpoint_url_get(provider), "POST", NULL,
&dpop) == FALSE)
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);
if (oidc_proto_token_endpoint_call(r, cfg, provider, params, basic_auth, bearer_auth, dpop, &response,
response_hdrs) == FALSE)
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;
}
json_decref(j_result);

if (oidc_util_decode_json_and_check_error(r, response, &j_result) == FALSE)
goto end;
Expand Down
Loading

0 comments on commit 336ba21

Please sign in to comment.