Skip to content

Commit d2eda00

Browse files
committed
add skeleton for updated AWS ALB JWK retrieval
which supports key rotation, see: OpenIDC/mod_oauth2#73 Signed-off-by: Hans Zandbelt <hans.zandbelt@openidc.com>
1 parent bde2985 commit d2eda00

File tree

5 files changed

+105
-15
lines changed

5 files changed

+105
-15
lines changed

ChangeLog

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
02/10/2025
2+
- add skeleton for updated AWS ALB JWK retrieval which supports key rotation
3+
see: https://github.com/OpenIDC/mod_oauth2/issues/73
4+
15
01/02/2024
26
- update copyright year to 2025
37

src/cfg/verify.c

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#define OAUTH2_JOSE_VERIFY_JWK_JWK_STR "jwk"
3737
#define OAUTH2_JOSE_VERIFY_JWK_JWKS_URI_STR "jwks_uri"
3838
#define OAUTH2_JOSE_VERIFY_JWK_ECKEY_URI_STR "eckey_uri"
39+
#define OAUTH2_JOSE_VERIFY_JWK_AWS_ALB_STR "aws_alb"
3940
#define OAUTH2_CFG_VERIFY_INTROSPECT_URL_STR "introspect"
4041
#define OAUTH2_CFG_VERIFY_METADATA_URL_STR "metadata"
4142

@@ -50,6 +51,7 @@ static oauth2_cfg_set_options_ctx_t _oauth2_cfg_verify_options_set[] = {
5051
{ OAUTH2_JOSE_VERIFY_JWK_JWK_STR, oauth2_jose_verify_options_jwk_set_jwk },
5152
{ OAUTH2_JOSE_VERIFY_JWK_JWKS_URI_STR, oauth2_jose_verify_options_jwk_set_jwks_uri },
5253
{ OAUTH2_JOSE_VERIFY_JWK_ECKEY_URI_STR, oauth2_jose_verify_options_jwk_set_eckey_uri },
54+
{ OAUTH2_JOSE_VERIFY_JWK_AWS_ALB_STR, oauth2_jose_verify_options_jwk_set_aws_alb },
5355
{ OAUTH2_CFG_VERIFY_INTROSPECT_URL_STR, oauth2_verify_options_set_introspect_url },
5456
{ OAUTH2_CFG_VERIFY_METADATA_URL_STR, oauth2_verify_options_set_metadata_url },
5557
{ NULL, NULL }

src/jose.c

+89-11
Original file line numberDiff line numberDiff line change
@@ -707,13 +707,17 @@ void oauth2_jose_jwk_list_free(oauth2_log_t *log, oauth2_jose_jwk_list_t *keys)
707707

708708
static oauth2_jose_jwk_list_t *
709709
oauth2_jose_jwks_list_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *,
710-
bool *);
710+
bool *, const cjose_header_t *);
711711
static oauth2_jose_jwk_list_t *
712712
oauth2_jose_jwks_uri_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *,
713-
bool *);
713+
bool *, const cjose_header_t *);
714714
static oauth2_jose_jwk_list_t *
715715
oauth2_jose_jwks_eckey_url_resolve(oauth2_log_t *,
716-
oauth2_jose_jwks_provider_t *, bool *);
716+
oauth2_jose_jwks_provider_t *, bool *,
717+
const cjose_header_t *);
718+
static oauth2_jose_jwk_list_t *
719+
oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *,
720+
bool *, const cjose_header_t *);
717721

718722
static oauth2_jose_jwks_provider_t *
719723
_oauth2_jose_jwks_provider_init(oauth2_log_t *log,
@@ -737,6 +741,10 @@ _oauth2_jose_jwks_provider_init(oauth2_log_t *log,
737741
provider->jwks_uri = oauth2_uri_ctx_init(log);
738742
provider->resolve = oauth2_jose_jwks_eckey_url_resolve;
739743
break;
744+
case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB:
745+
provider->resolve = oauth2_jose_jwks_aws_alb_resolve;
746+
provider->alb_arn = NULL;
747+
break;
740748
}
741749

742750
return provider;
@@ -765,6 +773,9 @@ _oauth2_jose_jwks_provider_clone(oauth2_log_t *log,
765773
case OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI:
766774
dst->jwks_uri = oauth2_uri_ctx_clone(log, src->jwks_uri);
767775
break;
776+
case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB:
777+
dst->alb_arn = oauth2_strdup(src->alb_arn);
778+
break;
768779
}
769780

770781
end:
@@ -790,6 +801,10 @@ _oauth2_jose_jwks_provider_free(oauth2_log_t *log,
790801
if (provider->jwks_uri)
791802
oauth2_uri_ctx_free(log, provider->jwks_uri);
792803
break;
804+
case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB:
805+
if (provider->alb_arn)
806+
oauth2_mem_free(provider->alb_arn);
807+
break;
793808
}
794809

795810
oauth2_mem_free(provider);
@@ -1292,7 +1307,7 @@ bool oauth2_jose_jwt_verify(oauth2_log_t *log,
12921307
if (jwt_verify_ctx) {
12931308

12941309
keys = jwt_verify_ctx->jwks_provider->resolve(
1295-
log, jwt_verify_ctx->jwks_provider, &refresh);
1310+
log, jwt_verify_ctx->jwks_provider, &refresh, hdr);
12961311

12971312
ctx.jws = jws;
12981313
ctx.kid = cjose_header_get(hdr, "kid", &err);
@@ -1309,7 +1324,7 @@ bool oauth2_jose_jwt_verify(oauth2_log_t *log,
13091324
if (keys)
13101325
oauth2_jose_jwk_list_free(log, keys);
13111326
keys = jwt_verify_ctx->jwks_provider->resolve(
1312-
log, jwt_verify_ctx->jwks_provider, &refresh);
1327+
log, jwt_verify_ctx->jwks_provider, &refresh, hdr);
13131328
_oauth2_jose_verification_keys_loop(
13141329
log, keys, _oauth2_jose_jwt_verify_jwk, &ctx);
13151330

@@ -1846,8 +1861,38 @@ _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri)
18461861
"eckey_uri");
18471862
}
18481863

1849-
static oauth2_jose_jwk_list_t *oauth2_jose_jwks_list_resolve(
1850-
oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh)
1864+
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb)
1865+
{
1866+
oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx;
1867+
char *rv = NULL;
1868+
oauth2_jose_jwt_verify_ctx_t *ptr = NULL;
1869+
1870+
oauth2_debug(log, "enter");
1871+
1872+
verify->callback = _oauth2_jose_jwt_verify_callback;
1873+
verify->ctx->callbacks = &oauth2_jose_jwt_verify_ctx_funcs;
1874+
verify->ctx->ptr = verify->ctx->callbacks->init(log);
1875+
ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr;
1876+
1877+
if (oauth2_jose_jwt_verify_set_options(
1878+
log, ptr, OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB, params) == false) {
1879+
rv = oauth2_strdup("oauth2_jose_jwt_verify_set_options failed");
1880+
goto end;
1881+
}
1882+
1883+
ptr->jwks_provider->alb_arn = oauth2_strdup(value);
1884+
1885+
end:
1886+
1887+
oauth2_debug(log, "leave: %s", rv);
1888+
1889+
return rv;
1890+
}
1891+
1892+
static oauth2_jose_jwk_list_t *
1893+
oauth2_jose_jwks_list_resolve(oauth2_log_t *log,
1894+
oauth2_jose_jwks_provider_t *provider,
1895+
bool *refresh, const cjose_header_t *hdr)
18511896
{
18521897
*refresh = false;
18531898
return oauth2_jose_jwk_list_clone(log, provider->jwks);
@@ -2171,22 +2216,55 @@ static oauth2_jose_jwk_list_t *_oauth2_jose_jwks_resolve_from_uri(
21712216
return dst;
21722217
}
21732218

2174-
static oauth2_jose_jwk_list_t *oauth2_jose_jwks_uri_resolve(
2175-
oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh)
2219+
static oauth2_jose_jwk_list_t *
2220+
oauth2_jose_jwks_uri_resolve(oauth2_log_t *log,
2221+
oauth2_jose_jwks_provider_t *provider,
2222+
bool *refresh, const cjose_header_t *hdr)
21762223
{
21772224
return _oauth2_jose_jwks_resolve_from_uri(
21782225
log, provider, refresh,
21792226
_oauth2_jose_jwks_uri_resolve_response_callback);
21802227
}
21812228

2182-
static oauth2_jose_jwk_list_t *oauth2_jose_jwks_eckey_url_resolve(
2183-
oauth2_log_t *log, oauth2_jose_jwks_provider_t *provider, bool *refresh)
2229+
static oauth2_jose_jwk_list_t *
2230+
oauth2_jose_jwks_eckey_url_resolve(oauth2_log_t *log,
2231+
oauth2_jose_jwks_provider_t *provider,
2232+
bool *refresh, const cjose_header_t *hdr)
21842233
{
21852234
return _oauth2_jose_jwks_resolve_from_uri(
21862235
log, provider, refresh,
21872236
_oauth2_jose_jwks_eckey_url_resolve_response_callback);
21882237
}
21892238

2239+
static oauth2_jose_jwk_list_t *
2240+
oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *log,
2241+
oauth2_jose_jwks_provider_t *provider,
2242+
bool *refresh, const cjose_header_t *hdr)
2243+
{
2244+
/*
2245+
* 1. pull the 'signer' and `kid` claims from the header (a typedef-ed
2246+
* JSON object)
2247+
* 2. check it against the configured provider->arb_arn value, and if
2248+
* they match:
2249+
* 3. construct the EC keys URL:
2250+
* https://public-keys.auth.elb.<region from
2251+
* ALB_ARN>.amazonaws.com/<kid>
2252+
* TODO: make the base URL configurable in
2253+
* oauth2_jose_verify_options_jwk_set_aws_alb and add a member
2254+
* alb_arn_base_url to oauth2_jose_jwks_provider_t
2255+
* 4. construct a temporary provider->jwks_uri
2256+
* 5. call:
2257+
* _oauth2_jose_jwks_resolve_from_uri(log, provider, refresh,
2258+
* oauth2_jose_jwks_eckey_url_resolve_response_callback);
2259+
* and save the result (oauth2_jose_jwk_list_t *)
2260+
* 6. free the temporary provider->jwks_uri (TODO: caching?)
2261+
* 7. return the result
2262+
*
2263+
* add unit tests
2264+
*/
2265+
return NULL;
2266+
}
2267+
21902268
/*
21912269
oauth2_jose_jwk_list_t *
21922270
oauth2_jose_jwks_resolve(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify,

src/jose_int.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,24 @@ typedef struct oauth2_uri_ctx_t {
4646
typedef enum oauth2_jose_jwks_provider_type_t {
4747
OAUTH2_JOSE_JWKS_PROVIDER_LIST,
4848
OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI,
49-
OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI
49+
OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI,
50+
OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB
5051
} oauth2_jose_jwks_provider_type_t;
5152

5253
typedef struct oauth2_jose_jwks_provider_t oauth2_jose_jwks_provider_t;
5354

5455
typedef oauth2_jose_jwk_list_t *(
5556
oauth2_jose_jwks_resolve_cb_t)(oauth2_log_t *,
56-
oauth2_jose_jwks_provider_t *, bool *);
57+
oauth2_jose_jwks_provider_t *, bool *,
58+
const cjose_header_t *hdr);
5759

5860
typedef struct oauth2_jose_jwks_provider_t {
5961
oauth2_jose_jwks_provider_type_t type;
6062
oauth2_jose_jwks_resolve_cb_t *resolve;
6163
union {
6264
oauth2_uri_ctx_t *jwks_uri;
6365
oauth2_jose_jwk_list_t *jwks;
66+
char *alb_arn;
6467
};
6568
// struct oauth2_jose_jwks_provider_t *next;
6669
} oauth2_jose_jwks_provider_t;
@@ -88,6 +91,7 @@ _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pubkey);
8891
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwk);
8992
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwks_uri);
9093
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri);
94+
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb);
9195

9296
char *oauth2_jose_resolve_from_uri(oauth2_log_t *log, oauth2_uri_ctx_t *uri_ctx,
9397
bool *refresh);

test/check_jose.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ START_TEST(test_jwks_resolve_uri)
368368
ck_assert_ptr_eq(rv, NULL);
369369

370370
ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr;
371-
list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh);
371+
list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh,
372+
NULL);
372373
ck_assert_ptr_ne(list, NULL);
373374

374375
oauth2_jose_jwk_list_free(_log, list);
@@ -390,7 +391,8 @@ START_TEST(test_jwk_resolve_plain)
390391
ck_assert_ptr_eq(rv, NULL);
391392

392393
ptr = (oauth2_jose_jwt_verify_ctx_t *)verify->ctx->ptr;
393-
list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh);
394+
list = ptr->jwks_provider->resolve(_log, ptr->jwks_provider, &refresh,
395+
NULL);
394396
ck_assert_ptr_ne(list, NULL);
395397

396398
oauth2_jose_jwk_list_free(_log, list);

0 commit comments

Comments
 (0)