187
185
unsigned char hash[sha1_len];
188
186
apr_sha1_final(hash, &sha1);
190
/* base64 encode the resulting hash and return it */
191
char *result = apr_palloc(r->pool, apr_base64_encode_len(sha1_len) + 1);
192
apr_base64_encode(result, (const char *) hash, sha1_len);
188
/* base64url-encode the resulting hash and return it */
190
oidc_base64url_encode(r, &result, (const char *) hash, sha1_len, TRUE);
195
* return the name for the state cookie
197
static char *oidc_get_state_cookie_name(request_rec *r, const char *state) {
198
return apr_psprintf(r->pool, "%s%s", OIDCStateCookiePrefix, state);
202
* return the static provider configuration, i.e. from a metadata URL or configuration primitives
204
static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *c, oidc_provider_t **provider) {
206
json_t *j_provider = NULL;
207
const char *s_json = NULL;
209
/* see if we should configure a static provider based on external (cached) metadata */
210
if ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) {
211
*provider = &c->provider;
215
c->cache->get(r, OIDC_CACHE_SECTION_PROVIDER, c->provider.metadata_url,
218
if (s_json == NULL) {
220
if (oidc_metadata_provider_retrieve(r, c, NULL,
221
c->provider.metadata_url, &j_provider, &s_json) == FALSE) {
222
oidc_error(r, "could not retrieve metadata from url: %s",
223
c->provider.metadata_url);
227
// TODO: make the expiry configurable
228
c->cache->set(r, OIDC_CACHE_SECTION_PROVIDER, c->provider.metadata_url,
230
apr_time_now() + apr_time_from_sec(OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT));
234
/* correct parsing and validation was already done when it was put in the cache */
235
j_provider = json_loads(s_json, 0, 0);
238
*provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t));
239
memcpy(*provider, &c->provider, sizeof(oidc_provider_t));
241
if (oidc_metadata_provider_parse(r, j_provider, *provider) == FALSE) {
242
oidc_error(r, "could not parse metadata from url: %s",
243
c->provider.metadata_url);
245
json_decref(j_provider);
249
json_decref(j_provider);
197
255
* return the oidc_provider_t struct for the specified issuer
199
static oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r, oidc_cfg *c, const char *issuer) {
257
static oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r,
258
oidc_cfg *c, const char *issuer) {
201
260
/* by default we'll assume that we're dealing with a single statically configured OP */
202
oidc_provider_t *provider = &c->provider;
261
oidc_provider_t *provider = NULL;
262
if (oidc_provider_static_config(r, c, &provider) == FALSE)
204
265
/* unless a metadata directory was configured, so we'll try and get the provider settings from there */
205
266
if (c->metadata_dir != NULL) {
207
268
/* try and get metadata from the metadata directory for the OP that sent this response */
208
if ((oidc_metadata_get(r, c, issuer, &provider)
209
== FALSE) || (provider == NULL)) {
269
if ((oidc_metadata_get(r, c, issuer, &provider) == FALSE)
270
|| (provider == NULL)) {
211
272
/* don't know nothing about this OP/issuer */
212
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
213
"oidc_get_provider_for_issuer: no provider metadata found for issuer \"%s\"",
273
oidc_error(r, "no provider metadata found for issuer \"%s\"",
226
286
static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
227
287
const char *state, oidc_proto_state **proto_state) {
229
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
230
"oidc_unsolicited_proto_state: entering");
289
oidc_debug(r, "enter");
232
291
apr_jwt_t *jwt = NULL;
233
292
if (apr_jwt_parse(r->pool, state, &jwt, c->private_keys,
234
293
c->provider.client_secret) == FALSE) {
235
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
236
"oidc_unsolicited_proto_state: could not parse JWT from state: invalid unsolicited response");
295
"could not parse JWT from state: invalid unsolicited response");
240
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
241
"oidc_unsolicited_proto_state: successfully parsed JWT from state");
299
oidc_debug(r, "successfully parsed JWT from state");
243
301
if (jwt->payload.iss == NULL) {
244
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
245
"oidc_unsolicited_proto_state: no \"iss\" could be retrieved from JWT state, aborting");
302
oidc_error(r, "no \"iss\" could be retrieved from JWT state, aborting");
246
303
apr_jwt_destroy(jwt);
257
314
char *rfp = NULL;
258
315
apr_jwt_get_string(r->pool, &jwt->payload.value, "rfp", &rfp);
259
316
if (rfp == NULL) {
260
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
261
"oidc_unsolicited_proto_state: no \"rfp\" claim could be retrieved from JWT state, aborting");
318
"no \"rfp\" claim could be retrieved from JWT state, aborting");
262
319
apr_jwt_destroy(jwt);
266
323
if (strcmp(rfp, "iss") != 0) {
267
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
268
"oidc_unsolicited_proto_state: \"rfp\" (%s) does not match \"iss\", aborting",
324
oidc_error(r, "\"rfp\" (%s) does not match \"iss\", aborting", rfp);
270
325
apr_jwt_destroy(jwt);
274
329
char *target_link_uri = NULL;
275
apr_jwt_get_string(r->pool, &jwt->payload.value, "target_uri", &target_link_uri);
330
apr_jwt_get_string(r->pool, &jwt->payload.value, "target_uri",
276
332
if (target_link_uri == NULL) {
277
if (c->default_url == NULL) {
278
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
279
"oidc_unsolicited_proto_state: no \"target_uri\" claim could be retrieved from JWT state and no OIDCDefaultURL is set, aborting");
333
if (c->default_sso_url == NULL) {
335
"no \"target_uri\" claim could be retrieved from JWT state and no OIDCDefaultURL is set, aborting");
280
336
apr_jwt_destroy(jwt);
283
target_link_uri = c->default_url;
339
target_link_uri = c->default_sso_url;
286
342
if (c->metadata_dir != NULL) {
287
343
if ((oidc_metadata_get(r, c, jwt->payload.iss, &provider) == FALSE)
288
344
|| (provider == NULL)) {
289
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
290
"oidc_unsolicited_proto_state: no provider metadata found for provider \"%s\"",
345
oidc_error(r, "no provider metadata found for provider \"%s\"",
291
346
jwt->payload.iss);
292
347
apr_jwt_destroy(jwt);
388
443
static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
389
444
const char *state, oidc_proto_state **proto_state) {
391
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
392
"oidc_restore_proto_state: entering");
446
oidc_debug(r, "enter");
448
const char *cookieName = oidc_get_state_cookie_name(r, state);
394
450
/* get the state cookie value first */
395
char *cookieValue = oidc_util_get_cookie(r, OIDCStateCookieName);
451
char *cookieValue = oidc_util_get_cookie(r, cookieName);
396
452
if (cookieValue == NULL) {
397
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
398
"oidc_restore_proto_state: no \"%s\" state cookie found",
399
OIDCStateCookieName);
453
oidc_error(r, "no \"%s\" state cookie found", cookieName);
400
454
return oidc_unsolicited_proto_state(r, c, state, proto_state);
403
457
/* clear state cookie because we don't need it anymore */
404
oidc_util_set_cookie(r, OIDCStateCookieName, "");
458
oidc_util_set_cookie(r, cookieName, "", 0);
406
460
/* decrypt the state obtained from the cookie */
407
461
char *svalue = NULL;
408
462
if (oidc_base64url_decode_decrypt_string(r, &svalue, cookieValue) <= 0)
411
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
412
"oidc_restore_proto_state: restored JSON state cookie value: %s",
465
oidc_debug(r, "restored JSON state cookie value: %s", svalue);
415
467
*proto_state = apr_pcalloc(r->pool, sizeof(oidc_proto_state));
416
468
oidc_proto_state *res = *proto_state;
457
516
/* check that the timestamp is not beyond the valid interval */
458
517
apr_time_t now = apr_time_sec(apr_time_now());
459
518
if (now > res->timestamp + c->state_timeout) {
460
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
461
"oidc_restore_proto_state: state has expired");
519
oidc_error(r, "state has expired");
462
520
json_decref(json);
466
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
467
"oidc_restore_proto_state: restored state: nonce=\"%s\", original_url=\"%s\", original_method=\"%s\", issuer=\"%s\", response_type=\%s\", response_mode=\"%s\", timestamp=%" APR_TIME_T_FMT,
468
res->nonce, res->original_url, res->original_method, res->issuer, res->response_type,
469
res->response_mode, res->timestamp);
525
"restored state: nonce=\"%s\", original_url=\"%s\", original_method=\"%s\", issuer=\"%s\", response_type=\%s\", response_mode=\"%s\", timestamp=%" APR_TIME_T_FMT,
526
res->nonce, res->original_url, res->original_method, res->issuer,
527
res->response_type, res->response_mode, res->timestamp);
471
529
json_decref(json);
492
550
"\"issuer\": \"%s\","
493
551
"\"response_type\": \"%s\","
494
552
"\"response_mode\": \"%s\","
553
"\"prompt\": \"%s\","
495
554
"\"timestamp\": %" APR_TIME_T_FMT "}", proto_state->nonce,
496
proto_state->original_url, proto_state->original_method, proto_state->issuer,
497
proto_state->response_type,
555
proto_state->original_url, proto_state->original_method,
556
proto_state->issuer, proto_state->response_type,
498
557
proto_state->response_mode ? proto_state->response_mode : "",
558
proto_state->prompt ? proto_state->prompt : "",
499
559
proto_state->timestamp);
501
561
/* encrypt the resulting JSON value */
502
562
char *cookieValue = NULL;
503
563
if (oidc_encrypt_base64url_encode_string(r, &cookieValue, plainText) <= 0) {
504
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
505
"oidc_authorization_request_set_cookie: oidc_encrypt_base64url_encode_string failed");
564
oidc_error(r, "oidc_encrypt_base64url_encode_string failed");
568
/* assemble the cookie name for the state cookie */
569
const char *cookieName = oidc_get_state_cookie_name(r, state);
509
571
/* set it as a cookie */
510
oidc_util_set_cookie(r, OIDCStateCookieName, cookieValue);
572
oidc_util_set_cookie(r, cookieName, cookieValue,
573
apr_time_now() + apr_time_from_sec(c->state_timeout));
640
702
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) {
641
703
/* set the id_token in the app headers + request state */
642
if (oidc_set_app_claims(r, cfg, session, OIDC_IDTOKEN_CLAIMS_SESSION_KEY) == FALSE)
704
if (oidc_set_app_claims(r, cfg, session,
705
OIDC_IDTOKEN_CLAIMS_SESSION_KEY) == FALSE)
643
706
return HTTP_INTERNAL_SERVER_ERROR;
646
709
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) {
647
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
648
"oidc_handle_existing_session: setting OIDC_id_token_payload header");
710
oidc_debug(r, "setting OIDC_id_token_payload header");
649
711
const char *s_id_token = NULL;
650
712
/* get the string-encoded JSON object from the session */
651
oidc_session_get(r, session, OIDC_IDTOKEN_CLAIMS_SESSION_KEY, &s_id_token);
713
oidc_session_get(r, session, OIDC_IDTOKEN_CLAIMS_SESSION_KEY,
652
715
/* pass it to the app in a header */
653
716
oidc_util_set_app_header(r, "id_token_payload", s_id_token, "OIDC_");
656
719
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) {
657
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
658
"oidc_handle_existing_session: setting OIDC_id_token header");
720
oidc_debug(r, "setting OIDC_id_token header");
659
721
const char *s_id_token = NULL;
660
722
/* get the compact serialized JWT from the session */
661
723
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &s_id_token);
707
769
oidc_cfg *c, const char *state, struct oidc_provider_t **provider,
708
770
oidc_proto_state **proto_state) {
710
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
711
"oidc_authorization_response_match_state: entering (state=%s)",
772
oidc_debug(r, "enter (state=%s)", state);
714
774
if ((state == NULL) || (apr_strnatcmp(state, "") == 0)) {
715
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
716
"oidc_authorization_response_match_state: state parameter is not set");
775
oidc_error(r, "state parameter is not set");
720
779
/* check the state parameter against what we stored in a cookie */
721
780
if (oidc_restore_proto_state(r, c, state, proto_state) == FALSE) {
722
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
723
"oidc_authorization_response_match_state: unable to restore state");
781
oidc_error(r, "unable to restore state");
727
*provider = oidc_get_provider_for_issuer(r, c, (*proto_state)->issuer);
785
*provider = oidc_get_provider_for_issuer(r, c, (*proto_state)->issuer);
729
787
return (*provider != NULL);
763
821
java_script = apr_psprintf(r->pool, java_script, original_url);
764
return oidc_util_http_sendstring(r, java_script, DONE);
822
return oidc_util_html_send(r, java_script, DONE);
826
* redirect the browser to the session logout endpoint
828
static int oidc_session_redirect_parent_window_to_logout(request_rec *r,
830
oidc_debug(r, "enter");
832
apr_psprintf(r->pool,
833
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
836
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
837
" <script type=\"text/javascript\">\n"
838
" window.top.location.href = '%s?session=logout';\n"
842
"</html>\n", c->redirect_uri);
843
return oidc_util_html_send(r, html, DONE);
847
* handle an error returned by the OP
849
static int oidc_authorization_response_error(request_rec *r, oidc_cfg *c,
850
oidc_proto_state *proto_state, const char *error,
851
const char *error_description) {
852
if ((proto_state->prompt != NULL)
853
&& (apr_strnatcmp(proto_state->prompt, "none") == 0)) {
854
return oidc_session_redirect_parent_window_to_logout(r, c);
856
return oidc_util_html_send_error(r, error, error_description, DONE);
769
860
* complete the handling of an authorization response by obtaining, parsing and verifying the
772
863
static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c,
773
864
session_rec *session, const char *state, char *code, char *id_token,
774
char *access_token, char *token_type, const char *response_mode) {
865
char *access_token, char *token_type, char *session_state,
866
const char *error, const char *error_description,
867
const char *response_mode) {
776
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
777
"oidc_handle_authorization_response: entering, state=%s, code=%s, id_token=%s, access_token=%s, token_type=%s",
778
state, code, id_token, access_token, token_type);
870
"enter, state=%s, code=%s, id_token=%s, access_token=%s, token_type=%s, session_state=%s, error=%s, error_description=%s, response_mode=%s",
871
state, code, id_token, access_token, token_type, session_state,
872
error, error_description, response_mode);
780
874
struct oidc_provider_t *provider = NULL;
781
875
oidc_proto_state *proto_state = NULL;
824
926
if (oidc_proto_resolve_code(r, c, provider, code, &c_id_token,
825
927
&c_access_token, &c_token_type) == FALSE) {
826
928
apr_jwt_destroy(jwt);
827
return HTTP_UNAUTHORIZED;
929
return oidc_authorization_response_error(r, c, proto_state,
930
"HTTP_UNAUTHORIZED", NULL);
830
933
/* validate the response on exchanging the code at the token endpoint */
831
if (oidc_proto_validate_code_response(r,
832
proto_state->response_type, &c_id_token, &c_access_token,
833
&c_token_type) == FALSE) {
934
if (oidc_proto_validate_code_response(r, proto_state->response_type,
935
&c_id_token, &c_access_token, &c_token_type) == FALSE) {
834
936
apr_jwt_destroy(jwt);
835
return HTTP_INTERNAL_SERVER_ERROR;
937
return oidc_authorization_response_error(r, c, proto_state,
938
"HTTP_INTERNAL_SERVER_ERROR", NULL);
838
941
/* use from the response whatever we still need */
857
960
if (jwt == NULL) {
858
961
if (oidc_proto_parse_idtoken(r, c, provider, id_token, nonce,
859
962
&remoteUser, &jwt, TRUE) == FALSE) {
860
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
861
"oidc_handle_authorization_response: could not parse or verify the id_token contents, return HTTP_UNAUTHORIZED");
862
return HTTP_UNAUTHORIZED;
964
"could not parse or verify the id_token contents, return HTTP_UNAUTHORIZED");
965
return oidc_authorization_response_error(r, c, proto_state,
966
"HTTP_UNAUTHORIZED", NULL);
867
971
if (jwt == NULL) {
868
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
869
"oidc_handle_authorization_response: no id_token was provided, return HTTP_UNAUTHORIZED");
870
return HTTP_UNAUTHORIZED;
972
oidc_error(r, "no id_token was provided, return HTTP_UNAUTHORIZED");
973
return oidc_authorization_response_error(r, c, proto_state,
974
"HTTP_UNAUTHORIZED", NULL);
873
977
/* validate the access token */
874
978
if (access_token != NULL) {
875
979
if (oidc_proto_validate_access_token(r, provider, jwt,
876
proto_state->response_type, access_token,
877
token_type) == FALSE) {
878
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
879
"oidc_handle_authorization_response: access_token did not validate, dropping it");
980
proto_state->response_type, access_token, token_type) == FALSE) {
981
oidc_warn(r, "access_token did not validate, dropping it");
880
982
access_token = NULL;
888
990
const char *claims = NULL;
889
991
json_t *j_claims = NULL;
890
if (oidc_proto_resolve_userinfo(r, c, provider, access_token, &claims,
891
&j_claims) == FALSE) {
992
if (provider->userinfo_endpoint_url == NULL) {
994
"not resolving user info claims because userinfo_endpoint is not set");
995
} else if (access_token == NULL) {
997
"not resolving user info claims because access_token is not provided");
998
} else if (oidc_proto_resolve_userinfo(r, c, provider, access_token,
999
&claims, &j_claims) == FALSE) {
1001
"resolving user info claims failed, nothing will be stored in the session");
1005
if ((proto_state->prompt != NULL)
1006
&& (apr_strnatcmp(proto_state->prompt, "none") == 0)) {
1007
// TOOD: actually need to compare sub? (need to store it in the session separately then
1008
//const char *sub = NULL;
1009
//oidc_session_get(r, session, "sub", &sub);
1010
//if (apr_strnatcmp(sub, jwt->payload.sub) != 0) {
1011
if (apr_strnatcmp(session->remote_user, remoteUser) != 0) {
1012
return oidc_authorization_response_error(r, c, proto_state,
1013
"sub changed!", NULL);
895
1017
/* set the resolved stuff in the session */
896
1018
session->remote_user = remoteUser;
953
1091
static int oidc_handle_post_authorization_response(request_rec *r, oidc_cfg *c,
954
1092
session_rec *session) {
956
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
957
"oidc_handle_post_authorization_response: entering");
1094
oidc_debug(r, "enter");
959
1096
/* initialize local variables */
960
1097
char *code = NULL, *state = NULL, *id_token = NULL, *access_token = NULL,
961
*token_type = NULL, *response_mode = NULL;
1098
*token_type = NULL, *response_mode = NULL, *session_state = NULL,
1099
*error = NULL, *error_description = NULL;
963
1101
/* read the parameters that are POST-ed to us */
964
1102
apr_table_t *params = apr_table_make(r->pool, 8);
965
1103
if (oidc_util_read_post(r, params) == FALSE) {
966
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
967
"oidc_handle_post_authorization_response: something went wrong when reading the POST parameters");
1104
oidc_error(r, "something went wrong when reading the POST parameters");
968
1105
return HTTP_INTERNAL_SERVER_ERROR;
971
1108
/* see if we've got any POST-ed data at all */
972
if (apr_is_empty_table(params)) {
973
return oidc_util_http_sendstring(r,
974
apr_psprintf(r->pool,
975
"mod_auth_openidc: you've hit an OpenID Connect callback URL with no parameters; this is an invalid request (you should not open this URL in your browser directly)"),
1109
if ((apr_table_elts(params)->nelts < 1)
1110
|| ((apr_table_elts(params)->nelts == 1)
1111
&& (apr_strnatcmp(apr_table_get(params, "response_mode"),
1112
"fragment") == 0))) {
1113
return oidc_util_html_send(r,
1114
"mod_auth_openidc: you've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you should not open this URL in your browser directly, or have the server administrator use a different OIDCRedirectURI setting.",
976
1115
HTTP_INTERNAL_SERVER_ERROR);
979
/* see if the response is an error response */
980
char *error = (char *) apr_table_get(params, "error");
981
char *error_description = (char *) apr_table_get(params,
982
"error_description");
984
return oidc_util_html_send_error(r, error, error_description, DONE);
986
1118
/* get the parameters */
987
1119
code = (char *) apr_table_get(params, "code");
988
1120
state = (char *) apr_table_get(params, "state");
1157
1297
/* create the state between request/response */
1158
oidc_proto_state proto_state = { nonce, original_url, method, provider->issuer,
1159
provider->response_type, provider->response_mode, apr_time_sec(apr_time_now()) };
1161
/* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */
1162
oidc_authorization_request_set_cookie(r, &proto_state);
1298
oidc_proto_state proto_state = { nonce, original_url, method,
1299
provider->issuer, provider->response_type, provider->response_mode,
1300
prompt, apr_time_sec(apr_time_now()) };
1164
1302
/* get a hash value that fingerprints the browser concatenated with the random input */
1165
1303
char *state = oidc_get_browser_state_hash(r, proto_state.nonce);
1305
/* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */
1306
oidc_authorization_request_set_cookie(r, c, state, &proto_state);
1168
1309
* TODO: I'd like to include the nonce all flows, including the "code" and "code token" flows
1169
1310
* but Google does not allow me to do that:
1170
1311
* Error: invalid_request: Parameter not allowed for this message type: nonce
1172
if ((strcmp(provider->issuer, "accounts.google.com") == 0)
1313
if ((apr_strnatcmp(provider->issuer, "accounts.google.com") == 0)
1173
1314
&& ((oidc_util_spaced_string_equals(r->pool,
1174
1315
provider->response_type, "code"))
1175
1316
|| (oidc_util_spaced_string_equals(r->pool,
1180
1321
* printout errors if Cookie settings are not going to work
1182
1323
apr_uri_t o_uri;
1324
memset(&o_uri, 0, sizeof(apr_uri_t));
1183
1325
apr_uri_t r_uri;
1326
memset(&r_uri, 0, sizeof(apr_uri_t));
1184
1327
apr_uri_parse(r->pool, original_url, &o_uri);
1185
1328
apr_uri_parse(r->pool, c->redirect_uri, &r_uri);
1186
if ( (apr_strnatcmp(o_uri.scheme, r_uri.scheme) != 0) && (apr_strnatcmp(r_uri.scheme, "https") == 0) ) {
1187
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "oidc_authenticate_user: the URL scheme (%s) of the configured OIDCRedirectURI does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!", r_uri.scheme, o_uri.scheme);
1329
if ((apr_strnatcmp(o_uri.scheme, r_uri.scheme) != 0)
1330
&& (apr_strnatcmp(r_uri.scheme, "https") == 0)) {
1332
"the URL scheme (%s) of the configured OIDCRedirectURI does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
1333
r_uri.scheme, o_uri.scheme);
1190
1336
if (c->cookie_domain == NULL) {
1191
1337
if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) {
1192
1338
char *p = strstr(o_uri.hostname, r_uri.hostname);
1193
if ( (p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
1194
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "oidc_authenticate_user: the URL hostname (%s) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!", r_uri.hostname, o_uri.hostname);
1339
if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
1341
"the URL hostname (%s) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
1342
r_uri.hostname, o_uri.hostname);
1198
1346
char *p = strstr(o_uri.hostname, c->cookie_domain);
1199
if ( (p == NULL) || (apr_strnatcmp(c->cookie_domain, p) != 0)) {
1200
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "oidc_authenticate_user: the domain (%s) configured in OIDCCookieDomain does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!", c->cookie_domain, o_uri.hostname, original_url);
1347
if ((p == NULL) || (apr_strnatcmp(c->cookie_domain, p) != 0)) {
1349
"the domain (%s) configured in OIDCCookieDomain does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!",
1350
c->cookie_domain, o_uri.hostname, original_url);
1204
1354
/* send off to the OpenID Connect Provider */
1205
1355
// TODO: maybe show intermediate/progress screen "redirecting to"
1206
return oidc_proto_authorization_request(r, provider, login_hint, c->redirect_uri, state, &proto_state);
1356
return oidc_proto_authorization_request(r, provider, login_hint,
1357
c->redirect_uri, state, &proto_state, id_token_hint,
1358
auth_request_params);
1280
1432
static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
1282
1434
/* variables to hold the values returned in the response */
1283
char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL;
1435
char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL,
1436
*auth_request_params = NULL;
1284
1437
oidc_provider_t *provider = NULL;
1286
1439
oidc_util_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer);
1287
1440
oidc_util_get_request_parameter(r, OIDC_DISC_RT_PARAM, &target_link_uri);
1288
1441
oidc_util_get_request_parameter(r, OIDC_DISC_LH_PARAM, &login_hint);
1442
oidc_util_get_request_parameter(r, OIDC_DISC_AR_PARAM,
1443
&auth_request_params);
1290
1445
// TODO: trim issuer/accountname/domain input and do more input validation
1292
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
1293
"oidc_handle_discovery_response: issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\"",
1447
oidc_debug(r, "issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\"",
1294
1448
issuer, target_link_uri, login_hint);
1296
1450
if (issuer == NULL) {
1297
return oidc_util_http_sendstring(r,
1451
return oidc_util_html_send(r,
1298
1452
"mod_auth_openidc: wherever you came from, it sent you here with the wrong parameters...",
1299
1453
HTTP_INTERNAL_SERVER_ERROR);
1302
1456
if (target_link_uri == NULL) {
1303
if (c->default_url == NULL) {
1304
return oidc_util_http_sendstring(r,
1305
"mod_auth_openidc: 3rd party initiated SSO to this module without specifying a \"target_link_uri\" parameter is not possible because OIDCDefaultURL is not set.",
1457
if (c->default_sso_url == NULL) {
1458
return oidc_util_html_send(r,
1459
"mod_auth_openidc: SSO to this module without specifying a \"target_link_uri\" parameter is not possible because OIDCDefaultURL is not set.",
1306
1460
HTTP_INTERNAL_SERVER_ERROR);
1308
target_link_uri = c->default_url;
1462
target_link_uri = c->default_sso_url;
1311
1465
/* do open redirect prevention */
1312
1466
if (oidc_target_link_uri_matches_configuration(r, c,
1313
1467
target_link_uri) == FALSE) {
1314
return oidc_util_http_sendstring(r,
1468
return oidc_util_html_send(r,
1315
1469
"mod_auth_openidc: \"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.",
1316
1470
HTTP_UNAUTHORIZED);
1377
1534
oidc_session_kill(r, session);
1380
/* pickup the URL where the user wants to go after logout */
1539
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
1542
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
1545
" <p>Logged Out</p>\n"
1548
return oidc_util_html_send(r, html, DONE);
1551
/* send the user to the specified where-to-go-after-logout URL */
1552
apr_table_add(r->headers_out, "Location", url);
1554
return HTTP_MOVED_TEMPORARILY;
1558
* perform (single) logout
1560
static int oidc_handle_logout(request_rec *r, oidc_cfg *c, session_rec *session) {
1562
/* pickup the command or URL where the user wants to go after logout */
1381
1564
oidc_util_get_request_parameter(r, "logout", &url);
1383
/* send him there */
1384
apr_table_add(r->headers_out, "Location", url);
1385
return HTTP_MOVED_TEMPORARILY;
1565
if ((url == NULL) || (apr_strnatcmp(url, "") == 0))
1566
url = c->default_sso_url;
1568
oidc_debug(r, "enter (url=%s)", url);
1571
if ((url != NULL) && (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS)) {
1572
const char *error_description = apr_psprintf(r->pool,
1573
"Logout URL malformed: %s", url);
1574
oidc_error(r, "%s", error_description);
1575
return oidc_util_html_send_error(r, url, error_description,
1576
HTTP_INTERNAL_SERVER_ERROR);
1579
const char *end_session_endpoint = NULL;
1580
oidc_session_get(r, session, OIDC_LOGOUT_ENDPOINT_SESSION_KEY,
1581
&end_session_endpoint);
1582
if (end_session_endpoint != NULL) {
1584
const char *id_token_hint = NULL;
1585
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &id_token_hint);
1587
char *logout_request = apr_psprintf(r->pool, "%s%s",
1588
end_session_endpoint,
1589
strchr(end_session_endpoint, '?') != NULL ? "&" : "?");
1590
logout_request = apr_psprintf(r->pool, "%sid_token_hint=%s",
1591
logout_request, oidc_util_escape_string(r, id_token_hint));
1594
logout_request = apr_psprintf(r->pool,
1595
"%s&post_logout_redirect_uri=%s", logout_request,
1596
oidc_util_escape_string(r, url));
1598
url = logout_request;
1601
return oidc_handle_logout_request(r, c, session, url);
1389
1605
* handle request for JWKs
1391
int oidc_handle_jwks(request_rec *r, oidc_cfg *c) {
1607
static int oidc_handle_jwks(request_rec *r, oidc_cfg *c) {
1393
1609
/* pickup requested JWKs type */
1394
1610
// char *jwks_type = NULL;
1395
1611
// oidc_util_get_request_parameter(r, "jwks", &jwks_type);
1397
1612
char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : [");
1398
1613
apr_hash_index_t *hi = NULL;
1399
1614
apr_byte_t first = TRUE;
1400
1615
/* loop over the claims in the JSON structure */
1401
1616
if (c->public_keys != NULL) {
1402
for (hi = apr_hash_first(r->pool, c->public_keys); hi; hi = apr_hash_next(hi)) {
1617
for (hi = apr_hash_first(r->pool, c->public_keys); hi; hi =
1618
apr_hash_next(hi)) {
1403
1619
const char *s_kid = NULL;
1404
1620
const char *s_jwk = NULL;
1405
1621
apr_hash_this(hi, (const void**) &s_kid, NULL, (void**) &s_jwk);
1406
jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",", s_jwk);
1622
jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",",
1411
1628
// TODO: send stuff if first == FALSE?
1412
1629
jwks = apr_psprintf(r->pool, "%s ] }", jwks);
1414
return oidc_util_http_sendstring(r, jwks, DONE);
1631
return oidc_util_http_send(r, jwks, strlen(jwks), "application/json", DONE);
1634
static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c,
1635
session_rec *session, const char *check_session_iframe) {
1637
oidc_debug(r, "enter");
1639
if (check_session_iframe == NULL) {
1640
oidc_debug(r, "no check_session_iframe configured for current OP");
1644
apr_table_add(r->headers_out, "Location", check_session_iframe);
1645
return HTTP_MOVED_TEMPORARILY;
1648
static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *c,
1649
session_rec *session, const char *client_id,
1650
const char *check_session_iframe) {
1652
oidc_debug(r, "enter");
1654
const char *iframe_contents =
1655
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
1658
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
1659
" <script type=\"text/javascript\">\n"
1660
" var targetOrigin = '%s';\n"
1661
" var message = '%s' + ' ' + '%s';\n"
1664
" function checkSession() {\n"
1665
" console.log('checkSession: posting ' + message + ' to ' + targetOrigin);\n"
1666
" var win = window.parent.document.getElementById('%s').contentWindow;\n"
1667
" win.postMessage( message, targetOrigin);\n"
1670
" function setTimer() {\n"
1671
" checkSession();\n"
1672
" timerID = setInterval('checkSession()', %s);\n"
1675
" function receiveMessage(e) {\n"
1676
" console.log('receiveMessage: ' + e.data + ' from ' + e.origin);\n"
1677
" if (e.origin !== targetOrigin ) {\n"
1678
" console.log('receiveMessage: cross-site scripting attack?');\n"
1681
" if (e.data != 'unchanged') {\n"
1682
" clearInterval(timerID);\n"
1683
" if (e.data == 'changed') {\n"
1684
" window.location.href = '%s?session=check';\n"
1686
" window.location.href = '%s?session=logout';\n"
1691
" window.addEventListener('message', receiveMessage, false);\n"
1695
" <body onload=\"setTimer()\"><p></p></body>\n"
1698
/* determine the origin for the check_session_iframe endpoint */
1699
char *origin = apr_pstrdup(r->pool, check_session_iframe);
1701
apr_uri_parse(r->pool, check_session_iframe, &uri);
1702
char *p = strstr(origin, uri.path);
1705
/* the element identifier for the OP iframe */
1706
const char *op_iframe_id = "openidc-op";
1708
/* restore the OP session_state from the session */
1709
const char *session_state = NULL;
1710
oidc_session_get(r, session, OIDC_SESSION_STATE_SESSION_KEY,
1712
if (session_state == NULL) {
1714
"no session_state found in the session; the OP does probably not support session management!?");
1718
char *s_poll_interval = NULL;
1719
oidc_util_get_request_parameter(r, "poll", &s_poll_interval);
1720
if (s_poll_interval == NULL)
1721
s_poll_interval = "3000";
1722
iframe_contents = apr_psprintf(r->pool, iframe_contents, origin, client_id,
1723
session_state, op_iframe_id, s_poll_interval, c->redirect_uri,
1726
return oidc_util_html_send(r, iframe_contents, DONE);
1730
* handle session management request
1732
static int oidc_handle_session_management(request_rec *r, oidc_cfg *c,
1733
session_rec *session) {
1735
const char *issuer = NULL, *id_token_hint = NULL, *client_id = NULL,
1736
*check_session_iframe = NULL;
1737
oidc_provider_t *provider = NULL;
1739
/* get the command passed to the session management handler */
1740
oidc_util_get_request_parameter(r, "session", &cmd);
1742
oidc_error(r, "session management handler called with no command");
1743
return HTTP_INTERNAL_SERVER_ERROR;
1746
/* see if this is a local logout during session management */
1747
if (apr_strnatcmp("logout", cmd) == 0) {
1749
"[session=logout] calling oidc_handle_logout_request because of session mgmt local logout call.");
1750
return oidc_handle_logout_request(r, c, session, c->default_slo_url);
1753
/* see if this is a request for the OP iframe */
1754
if (apr_strnatcmp("iframe_op", cmd) == 0) {
1755
oidc_session_get(r, session, OIDC_CHECK_IFRAME_SESSION_KEY,
1756
&check_session_iframe);
1757
if (check_session_iframe != NULL) {
1758
return oidc_handle_session_management_iframe_op(r, c, session,
1759
check_session_iframe);
1764
/* see if this is a request for the RP iframe */
1765
if (apr_strnatcmp("iframe_rp", cmd) == 0) {
1766
oidc_session_get(r, session, OIDC_CLIENTID_SESSION_KEY, &client_id);
1767
oidc_session_get(r, session, OIDC_CHECK_IFRAME_SESSION_KEY,
1768
&check_session_iframe);
1769
if ((client_id != NULL) && (check_session_iframe != NULL)) {
1770
return oidc_handle_session_management_iframe_rp(r, c, session,
1771
client_id, check_session_iframe);
1776
/* see if this is a request check the login state with the OP */
1777
if (apr_strnatcmp("check", cmd) == 0) {
1778
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &id_token_hint);
1779
oidc_session_get(r, session, OIDC_ISSUER_SESSION_KEY, &issuer);
1781
provider = oidc_get_provider_for_issuer(r, c, issuer);
1782
if ((id_token_hint != NULL) && (provider != NULL)) {
1783
return oidc_authenticate_user(r, c, provider,
1784
apr_psprintf(r->pool, "%s?session=iframe_rp",
1785
c->redirect_uri), NULL, id_token_hint, "none", NULL);
1788
"[session=check] calling oidc_handle_logout_request because no session found.");
1789
return oidc_session_redirect_parent_window_to_logout(r, c);
1792
/* handle failure in fallthrough */
1793
oidc_error(r, "unknown command: %s", cmd);
1795
return HTTP_INTERNAL_SERVER_ERROR;
1456
1842
/* check for "error" response */
1457
1843
if (oidc_util_request_has_parameter(r, "error")) {
1459
char *error = NULL, *descr = NULL;
1460
oidc_util_get_request_parameter(r, "error", &error);
1461
oidc_util_get_request_parameter(r, "error_description", &descr);
1463
/* send user facing error to browser */
1464
return oidc_util_html_send_error(r, error, descr, DONE);
1845
// char *error = NULL, *descr = NULL;
1846
// oidc_util_get_request_parameter(r, "error", &error);
1847
// oidc_util_get_request_parameter(r, "error_description", &descr);
1849
// /* send user facing error to browser */
1850
// return oidc_util_html_send_error(r, error, descr, DONE);
1851
oidc_handle_redirect_authorization_response(r, c, session);
1467
1854
/* something went wrong */
1468
return oidc_util_http_sendstring(r,
1855
return oidc_util_html_send(r,
1469
1856
apr_psprintf(r->pool,
1470
1857
"mod_auth_openidc: the OpenID Connect callback URL received an invalid request: %s",
1471
1858
r->args), HTTP_INTERNAL_SERVER_ERROR);
1564
1951
* get the claims and id_token from request state
1566
static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims, json_t **id_token) {
1953
static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims,
1954
json_t **id_token) {
1567
1955
const char *s_claims = oidc_request_state_get(r, OIDC_CLAIMS_SESSION_KEY);
1568
const char *s_id_token = oidc_request_state_get(r, OIDC_IDTOKEN_CLAIMS_SESSION_KEY);
1956
const char *s_id_token = oidc_request_state_get(r,
1957
OIDC_IDTOKEN_CLAIMS_SESSION_KEY);
1569
1958
json_error_t json_error;
1570
1959
if (s_claims != NULL) {
1571
1960
*claims = json_loads(s_claims, 0, &json_error);
1572
1961
if (*claims == NULL) {
1573
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1574
"oidc_authz_get_claims_and_idtoken: could not restore claims from request state: %s", json_error.text);
1962
oidc_error(r, "could not restore claims from request state: %s",
1577
1966
if (s_id_token != NULL) {
1578
1967
*id_token = json_loads(s_id_token, 0, &json_error);
1579
1968
if (*id_token == NULL) {
1580
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1581
"oidc_authz_get_claims_and_idtoken: could not restore id_token from request state: %s", json_error.text);
1969
oidc_error(r, "could not restore id_token from request state: %s",