68
68
/* read the parameters that are POST-ed to us */
69
69
apr_table_t *params = apr_table_make(r->pool, 8);
70
70
if (oidc_util_read_post(r, params) == FALSE) {
71
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
72
"oidc_proto_authorization_request: something went wrong when reading the POST parameters");
71
oidc_error(r, "something went wrong when reading the POST parameters");
73
72
return HTTP_INTERNAL_SERVER_ERROR;
76
// TODO: html encode names/values
77
75
const apr_array_header_t *arr = apr_table_elts(params);
78
76
const apr_table_entry_t *elts = (const apr_table_entry_t*) arr->elts;
81
79
for (i = 0; i < arr->nelts; i++) {
82
json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json, elts[i].key,
83
elts[i].val, i < arr->nelts - 1 ? "," : "");
80
json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json,
81
oidc_util_html_escape(r->pool, elts[i].key),
82
oidc_util_html_escape(r->pool, elts[i].val),
83
i < arr->nelts - 1 ? "," : "");
85
85
json = apr_psprintf(r->pool, "{ %s }", json);
104
104
"</html>\n", json, authorization_request);
106
return oidc_util_http_sendstring(r, java_script, DONE);
106
return oidc_util_html_send(r, java_script, DONE);
112
112
int oidc_proto_authorization_request(request_rec *r,
113
113
struct oidc_provider_t *provider, const char *login_hint,
114
114
const char *redirect_uri, const char *state,
115
oidc_proto_state *proto_state) {
115
oidc_proto_state *proto_state, const char *id_token_hint,
116
const char *auth_request_params) {
117
118
/* log some stuff */
118
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
119
"oidc_proto_authorization_request: entering (issuer=%s, redirect_uri=%s, original_url=%s, state=%s, nonce=%s)",
120
"enter, issuer=%s, redirect_uri=%s, original_url=%s, state=%s, nonce=%s",
120
121
provider->issuer, redirect_uri, proto_state->original_url, state,
121
122
proto_state->nonce);
155
156
authorization_request = apr_psprintf(r->pool, "%s&login_hint=%s",
156
157
authorization_request, oidc_util_escape_string(r, login_hint));
158
/* add any custom authorization request parameters if configured */
159
/* add the id_token_hint if provided */
160
if (id_token_hint != NULL)
161
authorization_request = apr_psprintf(r->pool, "%s&id_token_hint=%s",
162
authorization_request,
163
oidc_util_escape_string(r, id_token_hint));
165
/* add the prompt setting if provided (e.g. "none" for no-GUI checks) */
166
if (proto_state->prompt != NULL)
167
authorization_request = apr_psprintf(r->pool, "%s&prompt=%s",
168
authorization_request,
169
oidc_util_escape_string(r, proto_state->prompt));
171
/* add any statically configured custom authorization request parameters */
159
172
if (provider->auth_request_params != NULL) {
160
173
authorization_request = apr_psprintf(r->pool, "%s&%s",
161
174
authorization_request, provider->auth_request_params);
177
/* add any dynamically configured custom authorization request parameters */
178
if (auth_request_params != NULL) {
179
authorization_request = apr_psprintf(r->pool, "%s&%s",
180
authorization_request, auth_request_params);
164
183
/* preserve POSTed form parameters if enabled */
165
184
if (apr_strnatcmp(proto_state->original_method, "form_post") == 0)
166
185
return oidc_proto_authorization_request_post_preserve(r,
170
189
apr_table_add(r->headers_out, "Location", authorization_request);
172
191
/* some more logging */
173
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
174
"oidc_proto_authorization_request: adding outgoing header: Location: %s",
192
oidc_debug(r, "adding outgoing header: Location: %s",
175
193
authorization_request);
177
195
/* and tell Apache to return an HTTP Redirect (302) message */
211
229
/* see if we have this nonce cached already */
212
230
const char *replay = NULL;
213
cfg->cache->get(r, nonce, &replay);
231
cfg->cache->get(r, OIDC_CACHE_SECTION_NONCE, nonce, &replay);
214
232
if (replay != NULL) {
215
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
216
"oidc_proto_validate_nonce: the nonce value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
234
"the nonce value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
223
241
apr_jwt_get_string(r->pool, &jwt->payload.value, "nonce", &j_nonce);
225
243
if (j_nonce == NULL) {
226
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
227
"oidc_proto_validate_nonce: id_token JSON payload did not contain a \"nonce\" string");
245
"id_token JSON payload did not contain a \"nonce\" string");
231
249
/* see if the nonce in the id_token matches the one that we sent in the authorization request */
232
250
if (apr_strnatcmp(nonce, j_nonce) != 0) {
233
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
234
"oidc_proto_validate_nonce: the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)",
252
"the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)",
244
262
provider->idtoken_iat_slack * 2 + 10);
246
264
/* store it in the cache for the calculated duration */
247
cfg->cache->set(r, nonce, nonce, apr_time_now() + nonce_cache_duration);
265
cfg->cache->set(r, OIDC_CACHE_SECTION_NONCE, nonce, nonce,
266
apr_time_now() + nonce_cache_duration);
249
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
250
"oidc_proto_validate_nonce: nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds",
269
"nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds",
251
270
nonce, apr_time_sec(nonce_cache_duration));
268
287
* the same as the sole audience.
270
289
if ((azp != NULL) && (apr_strnatcmp(azp, provider->client_id) != 0)) {
271
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
272
"oidc_proto_validate_aud_and_azp: the \"azp\" claim (%s) is present in the id_token, but is not equal to the configured client_id (%s)",
291
"the \"azp\" claim (%s) is present in the id_token, but is not equal to the configured client_id (%s)",
273
292
azp, provider->client_id);
284
303
/* a single-valued audience must be equal to our client_id */
285
304
if (apr_strnatcmp(json_string_value(aud), provider->client_id)
287
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
288
"oidc_proto_validate_aud_and_azp: the configured client_id (%s) did not match the \"aud\" claim value (%s) in the id_token",
307
"the configured client_id (%s) did not match the \"aud\" claim value (%s) in the id_token",
289
308
provider->client_id, json_string_value(aud));
294
313
} else if (json_is_array(aud)) {
296
315
if ((json_array_size(aud) > 1) && (azp == NULL)) {
297
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
298
"oidc_proto_validate_aud_and_azp: the \"aud\" claim value in the id_token is an array with more than 1 element, but \"azp\" claim is not present (a SHOULD in the spec...)");
317
"the \"aud\" claim value in the id_token is an array with more than 1 element, but \"azp\" claim is not present (a SHOULD in the spec...)");
301
320
if (oidc_util_json_array_has_value(r, aud,
302
321
provider->client_id) == FALSE) {
303
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
304
"oidc_proto_validate_aud_and_azp: our configured client_id (%s) could not be found in the array of values for \"aud\" claim",
323
"our configured client_id (%s) could not be found in the array of values for \"aud\" claim",
305
324
provider->client_id);
309
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
310
"oidc_proto_validate_aud_and_azp: id_token JSON payload \"aud\" claim is not a string nor an array");
329
"id_token JSON payload \"aud\" claim is not a string nor an array");
315
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
316
"oidc_proto_validate_aud_and_azp: id_token JSON payload did not contain an \"aud\" claim");
334
oidc_error(r, "id_token JSON payload did not contain an \"aud\" claim");
326
344
apr_byte_t oidc_proto_validate_iat(request_rec *r, oidc_provider_t *provider,
327
345
apr_jwt_t *jwt) {
328
346
if (jwt->payload.iat == APR_JWT_CLAIM_TIME_EMPTY) {
329
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
330
"oidc_proto_validate_iat: id_token JSON payload did not contain an \"iat\" number value");
348
"id_token JSON payload did not contain an \"iat\" number value");
334
352
/* check if this id_token has been issued just now +- slack (default 10 minutes) */
335
353
if ((apr_time_now() - apr_time_from_sec(provider->idtoken_iat_slack))
336
354
> jwt->payload.iat) {
337
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
338
"oidc_proto_validate_iat: \"iat\" validation failure (%" APR_TIME_T_FMT "): JWT was issued more than %d seconds ago",
356
"\"iat\" validation failure (%" APR_TIME_T_FMT "): JWT was issued more than %d seconds ago",
339
357
jwt->payload.iat, provider->idtoken_iat_slack);
342
360
if ((apr_time_now() + apr_time_from_sec(provider->idtoken_iat_slack))
343
361
< jwt->payload.iat) {
344
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
345
"oidc_proto_validate_iat: \"iat\" validation failure (%" APR_TIME_T_FMT "): JWT was issued more than %d seconds in the future",
363
"\"iat\" validation failure (%" APR_TIME_T_FMT "): JWT was issued more than %d seconds in the future",
346
364
jwt->payload.iat, provider->idtoken_iat_slack);
356
374
apr_byte_t oidc_proto_validate_exp(request_rec *r, apr_jwt_t *jwt) {
357
375
if (apr_time_now() > jwt->payload.exp) {
358
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
359
"oidc_proto_validate_exp: \"exp\" validation failure (%" APR_TIME_T_FMT "): JWT expired",
377
"\"exp\" validation failure (%" APR_TIME_T_FMT "): JWT expired",
360
378
jwt->payload.exp);
372
390
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
373
391
&auth_openidc_module);
375
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
376
"oidc_proto_validate_idtoken: entering jwt.header=\"%s\", jwt.payload=\%s\", nonce=%s",
393
oidc_debug(r, "enter, jwt.header=\"%s\", jwt.payload=\%s\", nonce=%s",
377
394
jwt->header.value.str, jwt->payload.value.str, nonce);
379
396
/* if a nonce is not passed, we're doing a ("code") flow where the nonce is optional */
386
403
/* issuer is mandatory in id_token */
387
404
if (jwt->payload.iss == NULL) {
388
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
389
"oidc_proto_validate_idtoken: response JSON object did not contain an \"iss\" string");
405
oidc_error(r, "response JSON object did not contain an \"iss\" string");
393
409
/* check if the issuer matches the requested value */
394
410
if (oidc_util_issuer_match(provider->issuer, jwt->payload.iss) == FALSE) {
395
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
396
"oidc_proto_validate_idtoken: configured issuer (%s) does not match received \"iss\" value in id_token (%s)",
412
"configured issuer (%s) does not match received \"iss\" value in id_token (%s)",
397
413
provider->issuer, jwt->payload.iss);
409
425
/* check if the required-by-spec "sub" claim is present */
410
426
if (jwt->payload.sub == NULL) {
411
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
412
"oidc_proto_validate_idtoken: id_token JSON payload did not contain the required-by-spec \"sub\" string value");
428
"id_token JSON payload did not contain the required-by-spec \"sub\" string value");
431
447
char *x5t = NULL;
432
448
apr_jwt_get_string(r->pool, &jwt_hdr->value, "x5t", &x5t);
434
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
435
"oidc_proto_get_key_from_jwks: search for kid \"%s\" or thumbprint x5t \"%s\"",
450
oidc_debug(r, "search for kid \"%s\" or thumbprint x5t \"%s\"",
436
451
jwt_hdr->kid, x5t);
438
453
/* get the "keys" JSON array from the JWKs object */
439
454
json_t *keys = json_object_get(j_jwks, "keys");
440
455
if ((keys == NULL) || !(json_is_array(keys))) {
441
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
442
"oidc_proto_get_key_from_jwks: \"keys\" array element is not a JSON array");
456
oidc_error(r, "\"keys\" array element is not a JSON array");
452
466
/* check that it is a JSON object */
453
467
if (!json_is_object(elem)) {
454
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
455
"oidc_proto_get_key_from_jwks: \"keys\" array element is not a JSON object, skipping");
469
"\"keys\" array element is not a JSON object, skipping");
465
479
/* see if we were looking for a specific kid, if not we'll return the first one found */
466
480
if ((jwt_hdr->kid == NULL) && (x5t == NULL)) {
467
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
468
"oidc_proto_get_key_from_jwks: no kid/x5t to match, return first key found");
481
oidc_debug(r, "no kid/x5t to match, return first key found");
470
483
apr_jwk_parse_json(r->pool, elem, NULL, result);
476
489
if ((ekid != NULL) && json_is_string(ekid) && (jwt_hdr->kid != NULL)) {
477
490
/* compare the requested kid against the current element */
478
491
if (apr_strnatcmp(jwt_hdr->kid, json_string_value(ekid)) == 0) {
479
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
480
"oidc_proto_get_key_from_jwks: found matching kid: \"%s\"",
492
oidc_debug(r, "found matching kid: \"%s\"", jwt_hdr->kid);
483
494
apr_jwk_parse_json(r->pool, elem, NULL, result);
490
501
if ((ex5t != NULL) && json_is_string(ex5t) && (x5t != NULL)) {
491
502
/* compare the requested kid against the current element */
492
503
if (apr_strnatcmp(x5t, json_string_value(ex5t)) == 0) {
493
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
494
"oidc_proto_get_key_from_jwks: found matching x5t: \"%s\"",
504
oidc_debug(r, "found matching x5t: \"%s\"", x5t);
497
506
apr_jwk_parse_json(r->pool, elem, NULL, result);
516
525
/* get the set of JSON Web Keys for this provider (possibly by downloading them from the specified provider->jwk_uri) */
517
526
oidc_metadata_jwks_get(r, cfg, provider, &j_jwks, refresh);
518
527
if (j_jwks == NULL) {
519
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
520
"oidc_proto_get_key_from_jwk_uri: could not resolve JSON Web Keys");
528
oidc_error(r, "could not resolve JSON Web Keys");
533
541
/* we did not get a key, but we have not refreshed the JWKs from the jwks_uri yet */
535
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
536
"oidc_proto_get_key_from_jwk_uri: could not find a key in the cached JSON Web Keys, doing a forced refresh");
544
"could not find a key in the cached JSON Web Keys, doing a forced refresh");
538
546
/* get the set of JSON Web Keys for this provider forcing a fresh download from the specified provider->jwk_uri) */
540
548
oidc_metadata_jwks_get(r, cfg, provider, &j_jwks, refresh);
541
549
if (j_jwks == NULL) {
542
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
543
"oidc_proto_get_key_from_jwk_uri: could not refresh JSON Web Keys");
550
oidc_error(r, "could not refresh JSON Web Keys");
568
575
if (apr_jws_signature_is_hmac(r->pool, jwt)) {
570
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
571
"oidc_proto_idtoken_verify_signature: verifying HMAC signature on id_token: header=%s, message=%s",
578
"verifying HMAC signature on id_token: header=%s, message=%s",
572
579
jwt->header.value.str, jwt->message);
574
581
result = apr_jws_verify_hmac(r->pool, jwt, provider->client_secret,
588
595
if (jwk != NULL) {
590
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
591
"oidc_proto_idtoken_verify_signature: verifying RSA/EC signature on id_token: header=%s, message=%s",
598
"verifying RSA/EC signature on id_token: header=%s, message=%s",
592
599
jwt->header.value.str, jwt->message);
605
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
606
"oidc_proto_idtoken_verify_signature: could not find a key in the JSON Web Keys");
612
oidc_warn(r, "could not find a key in the JSON Web Keys");
608
614
if (*refresh == FALSE) {
610
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
611
"oidc_proto_idtoken_verify_signature: force refresh of the JWKS");
616
oidc_debug(r, "force refresh of the JWKS");
613
618
/* do it again, forcing a JWKS refresh */
622
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
623
"oidc_proto_idtoken_verify_signature: cannot verify id_token; unsupported algorithm \"%s\", must be RSA or HMAC",
628
"cannot verify id_token; unsupported algorithm \"%s\", must be RSA or HMAC",
624
629
jwt->header.alg);
628
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
629
"oidc_proto_idtoken_verify_signature: verification result of signature with algorithm \"%s\": %s",
633
oidc_debug(r, "verification result of signature with algorithm \"%s\": %s",
630
634
jwt->header.alg, (result == TRUE) ? "TRUE" : "FALSE");
655
659
apr_jwt_get_string(r->pool, &jwt->payload.value, claim_name, &username);
657
661
if (username == NULL) {
658
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
659
"oidc_proto_set_remote_user: OIDCRemoteUserClaim is set to \"%s\", but the id_token JSON payload did not contain a \"%s\" string",
663
"OIDCRemoteUserClaim is set to \"%s\", but the id_token JSON payload did not contain a \"%s\" string",
660
664
c->remote_user_claim, claim_name);
666
670
apr_psprintf(r->pool, "%s@%s", username, issuer) :
667
671
apr_pstrdup(r->pool, username);
669
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
670
"oidc_proto_set_remote_user: set remote_user to %s", *user);
673
oidc_debug(r, "set remote_user to \"%s\"", *user);
679
682
oidc_provider_t *provider, const char *id_token, const char *nonce,
680
683
char **user, apr_jwt_t **jwt, apr_byte_t is_code_flow) {
682
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
683
"oidc_proto_parse_idtoken: entering");
685
oidc_debug(r, "enter");
685
687
if (apr_jwt_parse(r->pool, id_token, jwt, cfg->private_keys,
686
688
provider->client_secret) == FALSE) {
687
689
if ((*jwt) && ((*jwt)->header.alg)
688
690
&& (apr_jwe_algorithm_is_supported(r->pool, (*jwt)->header.alg)
690
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
691
"oidc_proto_parse_idtoken: JWE content key encryption algorithm is not supported: %s",
693
"JWE content key encryption algorithm is not supported: %s",
692
694
(*jwt)->header.alg);
694
696
if ((*jwt) && ((*jwt)->header.enc)
695
697
&& (apr_jwe_encryption_is_supported(r->pool, (*jwt)->header.enc)
697
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
698
"oidc_proto_parse_idtoken: JWE encryption type is not supported: %s",
699
oidc_error(r, "JWE encryption type is not supported: %s",
699
700
(*jwt)->header.enc);
701
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
702
"oidc_proto_parse_idtoken: apr_jwt_parse failed for JWT with header: \"%s\"",
702
oidc_error(r, "apr_jwt_parse failed for JWT with header: \"%s\"",
703
703
apr_jwt_header_to_string(r->pool, id_token));
704
704
apr_jwt_destroy(*jwt);
708
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
709
"oidc_proto_parse_idtoken: successfully parsed (and possibly decrypted) JWT with header: \"%s\"",
709
"successfully parsed (and possibly decrypted) JWT with header: \"%s\"",
710
710
apr_jwt_header_to_string(r->pool, id_token));
712
712
// make signature validation exception for 'code' flow and the algorithm NONE
715
715
apr_byte_t refresh = FALSE;
716
716
if (oidc_proto_idtoken_verify_signature(r, cfg, provider, *jwt,
717
717
&refresh) == FALSE) {
718
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
719
"oidc_proto_parse_idtoken: id_token signature could not be validated, aborting");
719
"id_token signature could not be validated, aborting");
720
720
apr_jwt_destroy(*jwt);
725
725
/* this is where the meat is */
726
726
if (oidc_proto_validate_idtoken(r, provider, *jwt, nonce) == FALSE) {
727
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
728
"oidc_proto_parse_idtoken: id_token payload could not be validated, aborting");
727
oidc_error(r, "id_token payload could not be validated, aborting");
729
728
apr_jwt_destroy(*jwt);
733
732
if (oidc_proto_set_remote_user(r, cfg, provider, *jwt, user) == FALSE) {
734
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
735
"oidc_proto_parse_idtoken: remote user could not be set, aborting");
733
oidc_error(r, "remote user could not be set, aborting");
736
734
apr_jwt_destroy(*jwt);
740
738
/* log our results */
741
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
742
"oidc_proto_parse_idtoken: valid id_token for user \"%s\" (expires in %" APR_TIME_T_FMT " seconds)",
740
"valid id_token for user \"%s\" (expires in %" APR_TIME_T_FMT " seconds)",
743
741
*user, (*jwt)->payload.exp - apr_time_sec(apr_time_now()));
745
743
/* since we've made it so far, we may as well say it is a valid id_token */
754
752
/* we only support bearer/Bearer */
755
753
if ((token_type != NULL) && (apr_strnatcasecmp(token_type, "Bearer") != 0)
756
754
&& (provider->userinfo_endpoint_url != NULL)) {
757
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
758
"oidc_proto_check_token_type: token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal with Bearer authentication against a UserInfo endpoint!",
756
"token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal with Bearer authentication against a UserInfo endpoint!",
759
757
token_type, provider->userinfo_endpoint_url, provider->issuer);
769
767
oidc_provider_t *provider, const char *code, char **s_idtoken,
770
768
char **s_access_token, char **s_token_type) {
772
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
773
"oidc_proto_resolve_code: entering");
770
oidc_debug(r, "enter");
774
771
const char *response = NULL;
776
773
/* assemble the parameters for a call to the token endpoint */
782
779
/* see if we need to do basic auth or auth-through-post-params (both applied through the HTTP POST method though) */
783
780
const char *basic_auth = NULL;
784
if ((apr_strnatcmp(provider->token_endpoint_auth, "client_secret_basic"))
781
if ((provider->token_endpoint_auth == NULL)
782
|| (apr_strnatcmp(provider->token_endpoint_auth,
783
"client_secret_basic") == 0)) {
786
784
basic_auth = apr_psprintf(r->pool, "%s:%s", provider->client_id,
787
785
provider->client_secret);
790
788
apr_table_addn(params, "client_secret", provider->client_secret);
794
if (strcmp(provider->issuer, "https://sts.windows.net/b4ea3de6-839e-4ad1-ae78-c78e5c0cdc06/") == 0) {
795
apr_table_addn(params, "resource", "https://graph.windows.net");
791
/* see if we've configured any extra static parameters to the token endpoint */
792
if (provider->token_endpoint_params != NULL) {
793
const char *key, *val;
794
const char *p = provider->token_endpoint_params;
795
while (*p && (val = ap_getword(r->pool, &p, '&'))) {
796
key = ap_getword(r->pool, &val, '=');
797
ap_unescape_url((char *) key);
798
ap_unescape_url((char *) val);
799
apr_table_addn(params, key, val);
799
803
/* resolve the code against the token endpoint */
800
804
if (oidc_util_http_post_form(r, provider->token_endpoint_url, params,
801
805
basic_auth, NULL, provider->ssl_validate_server, &response,
802
806
cfg->http_timeout_long, cfg->outgoing_proxy) == FALSE) {
803
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
804
"oidc_proto_resolve_code: could not successfully resolve the \"code\" (%s) against the token endpoint (%s)",
808
"could not successfully resolve the \"code\" (%s) against the token endpoint (%s)",
805
809
code, provider->token_endpoint_url);
818
822
*s_access_token = apr_pstrdup(r->pool, json_string_value(access_token));
820
824
/* log and set the obtained acces_token */
821
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
822
"oidc_proto_resolve_code: returned access_token: %s",
825
oidc_debug(r, "returned access_token: %s", *s_access_token);
825
827
/* the provider must return the token type */
826
828
json_t *token_type = json_object_get(result, "token_type");
827
829
if ((token_type == NULL) || (!json_is_string(token_type))) {
828
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
829
"oidc_proto_resolve_code: response JSON object did not contain a token_type string");
831
"response JSON object did not contain a token_type string");
830
832
json_decref(result);
834
836
*s_token_type = apr_pstrdup(r->pool, json_string_value(token_type));
837
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
838
"oidc_proto_resolve_code: response JSON object did not contain an access_token string");
840
"response JSON object did not contain an access_token string");
841
843
/* get the id_token from the response */
844
846
*s_idtoken = apr_pstrdup(r->pool, json_string_value(id_token));
846
848
/* log and set the obtained id_token */
847
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
848
"oidc_proto_resolve_code: returned id_token: %s", *s_idtoken);
849
oidc_debug(r, "returned id_token: %s", *s_idtoken);
851
852
json_decref(result);
860
861
oidc_provider_t *provider, const char *access_token,
861
862
const char **response, json_t **claims) {
863
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
864
"oidc_resolve_userinfo: entering, endpoint=%s, access_token=%s",
864
oidc_debug(r, "enter, endpoint=%s, access_token=%s",
865
865
provider->userinfo_endpoint_url, access_token);
867
/* only do this if an actual endpoint was set */
868
if (provider->userinfo_endpoint_url == NULL)
871
/* only do this if we have an access_token */
872
if (access_token == NULL)
875
867
/* get the JSON response */
876
868
if (oidc_util_http_get(r, provider->userinfo_endpoint_url,
877
869
NULL, NULL, access_token, provider->ssl_validate_server, response,
891
883
// TODO: maybe show intermediate/progress screen "discovering..."
893
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
894
"oidc_proto_account_based_discovery: entering, acct=%s", acct);
885
oidc_debug(r, "enter, acct=%s", acct);
896
887
const char *resource = apr_psprintf(r->pool, "acct:%s", acct);
897
888
const char *domain = strrchr(acct, '@');
898
889
if (domain == NULL) {
899
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
900
"oidc_proto_account_based_discovery: invalid account name");
890
oidc_error(r, "invalid account name");
924
914
/* get the links parameter */
925
915
json_t *j_links = json_object_get(j_response, "links");
926
916
if ((j_links == NULL) || (!json_is_array(j_links))) {
927
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
928
"oidc_proto_account_based_discovery: response JSON object did not contain a \"links\" array");
917
oidc_error(r, "response JSON object did not contain a \"links\" array");
929
918
json_decref(j_response);
933
922
/* get the one-and-only object in the "links" array */
934
923
json_t *j_object = json_array_get(j_links, 0);
935
924
if ((j_object == NULL) || (!json_is_object(j_object))) {
936
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
937
"oidc_proto_account_based_discovery: response JSON object did not contain a JSON object as the first element in the \"links\" array");
926
"response JSON object did not contain a JSON object as the first element in the \"links\" array");
938
927
json_decref(j_response);
942
931
/* get the href from that object, which is the issuer value */
943
932
json_t *j_href = json_object_get(j_object, "href");
944
933
if ((j_href == NULL) || (!json_is_string(j_href))) {
945
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
946
"oidc_proto_account_based_discovery: response JSON object did not contain a \"href\" element in the first \"links\" array object");
935
"response JSON object did not contain a \"href\" element in the first \"links\" array object");
947
936
json_decref(j_response);
951
940
*issuer = apr_pstrdup(r->pool, json_string_value(j_href));
953
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
954
"oidc_proto_account_based_discovery: returning issuer \"%s\" for account \"%s\" after doing successful webfinger-based discovery",
943
"returning issuer \"%s\" for account \"%s\" after doing successful webfinger-based discovery",
957
946
json_decref(j_response);
962
951
int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c) {
964
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
965
"oidc_proto_javascript_implicit: entering");
953
oidc_debug(r, "enter");
967
955
// char *java_script = NULL;
968
956
// if (oidc_util_file_read(r, "/Users/hzandbelt/eclipse-workspace/mod_auth_openidc/src/implicit_post.html", &java_script) == FALSE) return HTTP_INTERNAL_SERVER_ERROR;
970
958
const char *java_script =
971
959
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
973
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
962
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
974
963
" <script type=\"text/javascript\">\n"
975
964
" function postOnLoad() {\n"
976
965
" encoded = location.hash.substring(1).split('&');\n"
1012
1001
/* calculate the base64url-encoded value of the hash */
1013
1002
char *encoded = NULL;
1014
oidc_base64url_encode(r, &encoded, calc,
1015
apr_jws_hash_length(alg) / 2, 1);
1003
oidc_base64url_encode(r, &encoded, calc, apr_jws_hash_length(alg) / 2, 1);
1017
1005
/* compare the calculated hash against the provided hash */
1018
1006
if ((apr_strnatcmp(encoded, hash) != 0)) {
1019
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1020
"oidc_proto_validate_hash: provided \"%s\" hash value (%s) does not match the calculated value (%s)",
1008
"provided \"%s\" hash value (%s) does not match the calculated value (%s)",
1021
1009
type, hash, encoded);
1025
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
1026
"oidc_proto_validate_hash: successfully validated the provided \"%s\" hash value (%s) against the calculated value (%s)",
1014
"successfully validated the provided \"%s\" hash value (%s) against the calculated value (%s)",
1027
1015
type, hash, encoded);
1053
1041
for (i = 0; i < required_for_flows->nelts; i++) {
1054
1042
if (oidc_util_spaced_string_equals(r->pool, response_type,
1055
1043
((const char**) required_for_flows->elts)[i])) {
1056
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1057
"oidc_proto_validate_hash_value: flow is \"%s\", but no %s found in id_token",
1044
oidc_warn(r, "flow is \"%s\", but no %s found in id_token",
1058
1045
response_type, key);
1133
1120
char **code, char **id_token, char **access_token, char **token_type,
1134
1121
const char *used_response_mode) {
1136
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
1137
"oidc_proto_validate_authorization_response: entering, response_type=%s, requested_response_mode=%s, code=%s, id_token=%s, access_token=%s, token_type=%s, used_response_mode=%s",
1124
"enter, response_type=%s, requested_response_mode=%s, code=%s, id_token=%s, access_token=%s, token_type=%s, used_response_mode=%s",
1138
1125
response_type, requested_response_mode, *code, *id_token,
1139
1126
*access_token, *token_type, used_response_mode);
1145
1132
* only warn because I'm not sure that most OPs will respect a requested
1146
1133
* response_mode and rather use the default for the flow
1148
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1149
"oidc_proto_validate_authorization_response: requested response_mode is \"%s\" the provider used \"%s\" for the authorization response...",
1136
"requested response_mode is \"%s\" the provider used \"%s\" for the authorization response...",
1150
1137
requested_response_mode, used_response_mode);
1156
1143
if (oidc_util_spaced_string_contains(r->pool, response_type, "code")) {
1158
1145
if (*code == NULL) {
1159
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1160
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but no \"code\" parameter found in the authorization response",
1147
"requested flow is \"%s\" but no \"code\" parameter found in the authorization response",
1161
1148
response_type);
1167
1154
if (*code != NULL) {
1168
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1169
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but there is a \"code\" parameter in the authorization response that will be dropped",
1156
"requested flow is \"%s\" but there is a \"code\" parameter in the authorization response that will be dropped",
1170
1157
response_type);
1178
1165
if (oidc_util_spaced_string_contains(r->pool, response_type, "id_token")) {
1180
1167
if (*id_token == NULL) {
1181
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1182
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but no \"id_token\" parameter found in the authorization response",
1169
"requested flow is \"%s\" but no \"id_token\" parameter found in the authorization response",
1183
1170
response_type);
1189
1176
if (*id_token != NULL) {
1190
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1191
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but there is an \"id_token\" parameter in the authorization response that will be dropped",
1178
"requested flow is \"%s\" but there is an \"id_token\" parameter in the authorization response that will be dropped",
1192
1179
response_type);
1193
1180
*id_token = NULL;
1201
1188
if (oidc_util_spaced_string_contains(r->pool, response_type, "token")) {
1203
1190
if (*access_token == NULL) {
1204
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1205
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but no \"access_token\" parameter found in the authorization response",
1192
"requested flow is \"%s\" but no \"access_token\" parameter found in the authorization response",
1206
1193
response_type);
1210
1197
if (*token_type == NULL) {
1211
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1212
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but no \"token_type\" parameter found in the authorization response",
1199
"requested flow is \"%s\" but no \"token_type\" parameter found in the authorization response",
1213
1200
response_type);
1219
1206
if (*access_token != NULL) {
1220
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1221
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but there is an \"access_token\" parameter in the authorization response that will be dropped",
1208
"requested flow is \"%s\" but there is an \"access_token\" parameter in the authorization response that will be dropped",
1222
1209
response_type);
1223
1210
*access_token = NULL;
1226
1213
if (*token_type != NULL) {
1227
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1228
"oidc_proto_validate_authorization_response: requested flow is \"%s\" but there is a \"token_type\" parameter in the authorization response that will be dropped",
1215
"requested flow is \"%s\" but there is a \"token_type\" parameter in the authorization response that will be dropped",
1229
1216
response_type);
1230
1217
*token_type = NULL;
1242
1229
const char *response_type, char **id_token, char **access_token,
1243
1230
char **token_type) {
1245
ap_log_rerror(APLOG_MARK, OIDC_DEBUG, 0, r,
1246
"oidc_proto_validate_code_response: entering");
1232
oidc_debug(r, "enter");
1249
1235
* check id_token parameter
1251
1237
if (!oidc_util_spaced_string_contains(r->pool, response_type, "id_token")) {
1253
1239
if (*id_token == NULL) {
1254
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1255
"oidc_proto_validate_code_response: requested flow is \"%s\" but no \"id_token\" parameter found in the code response",
1241
"requested flow is \"%s\" but no \"id_token\" parameter found in the code response",
1256
1242
response_type);
1262
1248
if (*id_token != NULL) {
1263
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1264
"oidc_proto_validate_code_response: requested flow is \"%s\" but there is an \"id_token\" parameter in the code response that will be dropped",
1250
"requested flow is \"%s\" but there is an \"id_token\" parameter in the code response that will be dropped",
1265
1251
response_type);
1266
1252
*id_token = NULL;
1274
1260
if (!oidc_util_spaced_string_contains(r->pool, response_type, "token")) {
1276
1262
if (*access_token == NULL) {
1277
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1278
"oidc_proto_validate_code_response: requested flow is \"%s\" but no \"access_token\" parameter found in the code response",
1264
"requested flow is \"%s\" but no \"access_token\" parameter found in the code response",
1279
1265
response_type);
1283
1269
if (*token_type == NULL) {
1284
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1285
"oidc_proto_validate_code_response: requested flow is \"%s\" but no \"token_type\" parameter found in the code response",
1271
"requested flow is \"%s\" but no \"token_type\" parameter found in the code response",
1286
1272
response_type);
1292
1278
if (*access_token != NULL) {
1293
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1294
"oidc_proto_validate_code_response: requested flow is \"%s\" but there is an \"access_token\" parameter in the code response that will be dropped",
1280
"requested flow is \"%s\" but there is an \"access_token\" parameter in the code response that will be dropped",
1295
1281
response_type);
1296
1282
*access_token = NULL;
1299
1285
if (*token_type != NULL) {
1300
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1301
"oidc_proto_validate_code_response: requested flow is \"%s\" but there is a \"token_type\" parameter in the code response that will be dropped",
1287
"requested flow is \"%s\" but there is a \"token_type\" parameter in the code response that will be dropped",
1302
1288
response_type);
1303
1289
*token_type = NULL;