From b7d7f3209678396a87467ab5b1120eccfba23b70 Mon Sep 17 00:00:00 2001 From: Hans Zandbelt Date: Thu, 6 Jun 2024 19:43:10 +0200 Subject: [PATCH] refactor proto JWT create and sign/serialize Signed-off-by: Hans Zandbelt --- src/mod_auth_openidc.c | 33 +++--------------------- src/proto/auth.c | 16 +++--------- src/proto/dpop.c | 36 +++++--------------------- src/proto/jwt.c | 58 ++++++++++++++++++++++++++++++++++++++++++ src/proto/proto.h | 58 +++++++++++++++++++++++------------------- src/proto/request.c | 4 ++- 6 files changed, 105 insertions(+), 100 deletions(-) diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c index 44871062..7aec67ef 100644 --- a/src/mod_auth_openidc.c +++ b/src/mod_auth_openidc.c @@ -873,28 +873,9 @@ static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cf oidc_debug(r, "enter: %s", s_claims); - jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), -1, OIDC_JOSE_JWK_SIG_STR); - // TODO: detect at config time - if (jwk == NULL) { - oidc_error(r, "no RSA/EC private signing keys have been configured (in " OIDCPrivateKeyFiles ")"); - goto end; - } - - jwt = oidc_jwt_new(r->pool, TRUE, TRUE); - if (jwt == NULL) + if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, FALSE) == FALSE) goto end; - jwt->header.kid = apr_pstrdup(r->pool, jwk->kid); - - if (jwk->kty == CJOSE_JWK_KTY_RSA) - jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_RS256); - else if (jwk->kty == CJOSE_JWK_KTY_EC) - jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_ES256); - else { - oidc_error(r, "no usable RSA/EC signing keys has been configured (in " OIDCPrivateKeyFiles ")"); - goto end; - } - json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD, json_string(oidc_util_current_url(r, oidc_cfg_x_forwarded_headers_get(cfg)))); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, @@ -923,7 +904,7 @@ static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cf } if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_JTI) == NULL) { - oidc_util_generate_random_string(r, &jti, 16); + oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); } if (json_object_get(jwt->payload.value.json, OIDC_CLAIM_IAT) == NULL) { @@ -938,16 +919,8 @@ static apr_byte_t oidc_userinfo_create_signed_jwt(request_rec *r, oidc_cfg_t *cf OIDC_USERINFO_SIGNED_JWT_EXP_DEFAULT)); } - if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { - oidc_error(r, "oidc_jwt_sign failed: %s", oidc_jose_e2s(r->pool, err)); + if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, cser) == FALSE) goto end; - } - - *cser = oidc_jwt_serialize(r->pool, jwt, &err); - if (*cser == NULL) { - oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); - goto end; - } rv = TRUE; diff --git a/src/proto/auth.c b/src/proto/auth.c index da0f72ca..c9540424 100644 --- a/src/proto/auth.c +++ b/src/proto/auth.c @@ -84,8 +84,6 @@ static apr_byte_t oidc_proto_endpoint_client_secret_post(request_rec *r, const c return TRUE; } -#define OIDC_PROTO_ASSERTION_JTI_LEN 16 - /* * helper function to create a JWT assertion for endpoint authentication */ @@ -95,7 +93,7 @@ static apr_byte_t oidc_proto_jwt_create(request_rec *r, const char *client_id, c oidc_jwt_t *jwt = *out; char *jti = NULL; - oidc_util_generate_random_string(r, &jti, OIDC_PROTO_ASSERTION_JTI_LEN); + oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS, json_string(client_id)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_SUB, json_string(client_id)); @@ -111,18 +109,10 @@ static apr_byte_t oidc_proto_jwt_create(request_rec *r, const char *client_id, c * helper function to add a JWT assertion to the HTTP request as endpoint authentication */ static apr_byte_t oidc_proto_jwt_sign_and_add(request_rec *r, apr_table_t *params, oidc_jwt_t *jwt, oidc_jwk_t *jwk) { - oidc_jose_error_t err; - - if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { - oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err)); - return FALSE; - } + char *cser = NULL; - char *cser = oidc_jwt_serialize(r->pool, jwt, &err); - if (cser == NULL) { - oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); + if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, &cser) == FALSE) return FALSE; - } apr_table_setn(params, OIDC_PROTO_CLIENT_ASSERTION_TYPE, OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER); apr_table_set(params, OIDC_PROTO_CLIENT_ASSERTION, cser); diff --git a/src/proto/dpop.c b/src/proto/dpop.c index 8126aaf9..47301c90 100644 --- a/src/proto/dpop.c +++ b/src/proto/dpop.c @@ -44,6 +44,8 @@ #include "proto/proto.h" #include "util.h" +#define OIDC_PROTO_DPOP_JWT_TYP "dpop+jwt" + /* * generate a DPoP proof for the specified URL/method/access_token */ @@ -61,32 +63,14 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c oidc_debug(r, "enter"); - jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), -1, OIDC_JOSE_JWK_SIG_STR); - if (jwk == NULL) { - oidc_debug(r, "no RSA/EC private signing keys have been configured (in " OIDCPrivateKeyFiles ")"); - goto end; - } - - jwt = oidc_jwt_new(r->pool, TRUE, TRUE); - if (jwt == NULL) - goto end; - - jwt->header.kid = apr_pstrdup(r->pool, jwk->kid); - - if (jwk->kty == CJOSE_JWK_KTY_RSA) - jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_PS256); - else if (jwk->kty == CJOSE_JWK_KTY_EC) - jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_ES256); - else { - oidc_error(r, "no usable RSA/EC signing keys has been configured (in " OIDCPrivateKeyFiles ")"); + if (oidc_proto_jwt_create_from_first_pkey(r, cfg, &jwk, &jwt, TRUE) == FALSE) goto end; - } - json_object_set_new(jwt->header.value.json, OIDC_CLAIM_TYP, json_string("dpop+jwt")); + json_object_set_new(jwt->header.value.json, OIDC_CLAIM_TYP, json_string(OIDC_PROTO_DPOP_JWT_TYP)); s_jwk = cjose_jwk_to_json(jwk->cjose_jwk, 0, &cjose_err); cjose_header_set_raw(jwt->header.value.json, OIDC_CLAIM_JWK, s_jwk, &cjose_err); - oidc_util_generate_random_string(r, &jti, 16); + oidc_util_generate_random_string(r, &jti, OIDC_PROTO_JWT_JTI_LEN); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI, json_string(jti)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_HTM, json_string(method)); json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_HTU, json_string(url)); @@ -101,16 +85,8 @@ char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, c json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ATH, json_string(ath)); } - if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { - oidc_error(r, "oidc_jwt_sign failed: %s", oidc_jose_e2s(r->pool, err)); - goto end; - } - - cser = oidc_jwt_serialize(r->pool, jwt, &err); - if (cser == NULL) { - oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); + if (oidc_proto_jwt_sign_and_serialize(r, jwk, jwt, &cser) == FALSE) goto end; - } end: diff --git a/src/proto/jwt.c b/src/proto/jwt.c index bfb23cd9..a055e8fc 100644 --- a/src/proto/jwt.c +++ b/src/proto/jwt.c @@ -244,3 +244,61 @@ char *oidc_proto_jwt_header_peek(request_rec *r, const char *compact_encoded_jwt } return result; } + +apr_byte_t oidc_proto_jwt_create_from_first_pkey(request_rec *r, oidc_cfg_t *cfg, oidc_jwk_t **jwk, oidc_jwt_t **jwt, + apr_byte_t use_psa_for_rsa) { + apr_byte_t rv = FALSE; + + oidc_debug(r, "enter"); + + *jwk = oidc_util_key_list_first(oidc_cfg_private_keys_get(cfg), -1, OIDC_JOSE_JWK_SIG_STR); + // TODO: detect at config time + if (jwk == NULL) { + oidc_error(r, "no RSA/EC private signing keys have been configured (in " OIDCPrivateKeyFiles ")"); + goto end; + } + + *jwt = oidc_jwt_new(r->pool, TRUE, TRUE); + if (*jwt == NULL) + goto end; + + (*jwt)->header.kid = apr_pstrdup(r->pool, (*jwk)->kid); + + if ((*jwk)->kty == CJOSE_JWK_KTY_RSA) + (*jwt)->header.alg = apr_pstrdup(r->pool, use_psa_for_rsa ? CJOSE_HDR_ALG_PS256 : CJOSE_HDR_ALG_RS256); + else if ((*jwk)->kty == CJOSE_JWK_KTY_EC) + (*jwt)->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_ES256); + else { + oidc_error(r, "no usable RSA/EC signing keys has been configured (in " OIDCPrivateKeyFiles ")"); + goto end; + } + + rv = TRUE; + +end: + + // also in case of errors, jwt will be destroyed in the caller function + return rv; +} + +apr_byte_t oidc_proto_jwt_sign_and_serialize(request_rec *r, oidc_jwk_t *jwk, oidc_jwt_t *jwt, char **cser) { + apr_byte_t rv = FALSE; + oidc_jose_error_t err; + + if (oidc_jwt_sign(r->pool, jwt, jwk, FALSE, &err) == FALSE) { + oidc_error(r, "oidc_jwt_sign failed: %s", oidc_jose_e2s(r->pool, err)); + goto end; + } + + *cser = oidc_jwt_serialize(r->pool, jwt, &err); + if (*cser == NULL) { + oidc_error(r, "oidc_jwt_serialize failed: %s", oidc_jose_e2s(r->pool, err)); + goto end; + } + + rv = TRUE; + +end: + + return rv; +} diff --git a/src/proto/proto.h b/src/proto/proto.h index 9d328739..4af33cce 100644 --- a/src/proto/proto.h +++ b/src/proto/proto.h @@ -120,32 +120,6 @@ typedef json_t oidc_proto_state_t; -// 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, - const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); -apr_byte_t oidc_proto_idtoken_validate_aud_and_azp(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, - oidc_jwt_payload_t *id_token_payload); -// non-static for test.c -apr_byte_t oidc_proto_idtoken_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, - const char *response_type, const char *access_token); -apr_byte_t oidc_proto_idtoken_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, - const char *response_type, const char *code); -apr_byte_t oidc_proto_idtoken_validate_nonce(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, - const char *nonce, oidc_jwt_t *jwt); - -// jwt.c -char *oidc_proto_jwt_header_peek(request_rec *r, const char *jwt, char **alg, char **enc, char **kid); -apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, - int ssl_validate_server, apr_hash_t *symmetric_keys, const char *alg); -apr_byte_t oidc_proto_jwt_validate(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, - apr_byte_t iat_is_mandatory, int iat_slack); - -// proto.c -apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len); -apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool); -apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow); -int oidc_proto_return_www_authenticate(request_rec *r, const char *error, const char *error_description); - // auth.c apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg_t *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, @@ -161,10 +135,36 @@ apr_byte_t oidc_proto_discovery_url_based(request_rec *r, oidc_cfg_t *cfg, const char *oidc_proto_dpop_create(request_rec *r, oidc_cfg_t *cfg, const char *url, const char *method, const char *access_token); +// 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, + const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow); +apr_byte_t oidc_proto_idtoken_validate_aud_and_azp(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, + oidc_jwt_payload_t *id_token_payload); +// non-static for test.c +apr_byte_t oidc_proto_idtoken_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, + const char *response_type, const char *access_token); +apr_byte_t oidc_proto_idtoken_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, + const char *response_type, const char *code); +apr_byte_t oidc_proto_idtoken_validate_nonce(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, + const char *nonce, oidc_jwt_t *jwt); + // jwks.c apr_byte_t oidc_proto_jwks_uri_keys(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, int ssl_validate_server, apr_hash_t *keys, apr_byte_t *force_refresh); +// jwt.c + +#define OIDC_PROTO_JWT_JTI_LEN 16 + +apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg_t *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, + int ssl_validate_server, apr_hash_t *symmetric_keys, const char *alg); +apr_byte_t oidc_proto_jwt_validate(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, + apr_byte_t iat_is_mandatory, int iat_slack); +char *oidc_proto_jwt_header_peek(request_rec *r, const char *jwt, char **alg, char **enc, char **kid); +apr_byte_t oidc_proto_jwt_create_from_first_pkey(request_rec *r, oidc_cfg_t *cfg, oidc_jwk_t **jwk, oidc_jwt_t **jwt, + apr_byte_t use_psa_for_rsa); +apr_byte_t oidc_proto_jwt_sign_and_serialize(request_rec *r, oidc_jwk_t *jwk, oidc_jwt_t *jwt, char **cser); + // pkce.c #define OIDC_PKCE_METHOD_PLAIN "plain" #define OIDC_PKCE_METHOD_S256 "S256" @@ -179,6 +179,12 @@ extern oidc_proto_pkce_t oidc_pkce_s256; const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state); void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state); +// proto.c +apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len); +apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool); +apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow); +int oidc_proto_return_www_authenticate(request_rec *r, const char *error, const char *error_description); + // request.c int oidc_proto_request_auth(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, diff --git a/src/proto/request.c b/src/proto/request.c index d2daf783..b32ceff7 100644 --- a/src/proto/request.c +++ b/src/proto/request.c @@ -503,6 +503,8 @@ static char *oidc_request_uri_request_object(request_rec *r, struct oidc_provide return serialized_request_object; } +#define OIDC_PROTO_REQUEST_URI_REF_LEN 16 + /* * generate a request object and pass it by reference in the authorization request */ @@ -527,7 +529,7 @@ static char *oidc_proto_request_uri_create(request_rec *r, struct oidc_provider_ char *request_uri = NULL; if (serialized_request_object != NULL) { char *request_ref = NULL; - if (oidc_util_generate_random_string(r, &request_ref, 16) == TRUE) { + if (oidc_util_generate_random_string(r, &request_ref, OIDC_PROTO_REQUEST_URI_REF_LEN) == TRUE) { oidc_cache_set_request_uri(r, request_ref, serialized_request_object, apr_time_now() + apr_time_from_sec(ttl)); request_uri = apr_psprintf(r->pool, "%s?%s=%s", resolver_url, OIDC_PROTO_REQUEST_URI,