Skip to content

Commit e9f7e92

Browse files
committed
release 2.1.0: add updated AWS ALB JWKs retrieval
supporting new "signer"/"region" logic and key rotation; closes: OpenIDC/mod_oauth2#73 Signed-off-by: Hans Zandbelt <[email protected]>
1 parent bde2985 commit e9f7e92

File tree

10 files changed

+234
-24
lines changed

10 files changed

+234
-24
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ reporting bugs, providing fixes, suggesting useful features or other:
1515
Pavel Anpin <https://github.com/anpin>
1616
smanolache <https://github.com/smanolache>
1717
pladen <https://github.com/pladen>
18+
Drew <https://github.com/drwxmrrs>

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
02/12/2025
2+
- add updated AWS ALB JWKs retrieval supporting new "signer"/"region" logic and key rotation
3+
closes: https://github.com/OpenIDC/mod_oauth2/issues/73
4+
- release 2.1.0
5+
16
01/02/2024
27
- update copyright year to 2025
38

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
AC_INIT([liboauth2],[2.1.0dev],[[email protected]])
1+
AC_INIT([liboauth2],[2.1.0],[[email protected]])
22

33
AM_INIT_AUTOMAKE([foreign no-define subdir-objects])
44
AC_CONFIG_MACRO_DIR([m4])

liboauth2_apache.pc.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ Name: liboauth2_apache
1111
URL: https://github.com/OpenIDC/liboauth2
1212
Description: Apache C bindings for liboauth2
1313
Version: @VERSION@
14-
Requires: liboauth2 >= 1.6.3, apr-1, apr-util-1
14+
Requires: liboauth2 >= 2.0.0, apr-1, apr-util-1
1515
Cflags: -I${includedir}
1616
Libs: -L${libdir} -loauth2_apache

liboauth2_nginx.pc.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ Name: liboauth2_nginx
1111
URL: https://github.com/OpenIDC/liboauth2
1212
Description: NGINX C bindings for liboauth2
1313
Version: @VERSION@
14-
Requires: liboauth2 >= 1.6.3
14+
Requires: liboauth2 >= 2.0.0
1515
Cflags: -I${includedir}
1616
Libs: -L${libdir} -loauth2_nginx

src/cfg/verify.c

Lines changed: 2 additions & 0 deletions
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

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -707,13 +707,15 @@ 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 *, 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 *, cjose_header_t *);
714+
static oauth2_jose_jwk_list_t *oauth2_jose_jwks_eckey_url_resolve(
715+
oauth2_log_t *, oauth2_jose_jwks_provider_t *, bool *, cjose_header_t *);
714716
static oauth2_jose_jwk_list_t *
715-
oauth2_jose_jwks_eckey_url_resolve(oauth2_log_t *,
716-
oauth2_jose_jwks_provider_t *, bool *);
717+
oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *, oauth2_jose_jwks_provider_t *,
718+
bool *, cjose_header_t *);
717719

718720
static oauth2_jose_jwks_provider_t *
719721
_oauth2_jose_jwks_provider_init(oauth2_log_t *log,
@@ -737,6 +739,12 @@ _oauth2_jose_jwks_provider_init(oauth2_log_t *log,
737739
provider->jwks_uri = oauth2_uri_ctx_init(log);
738740
provider->resolve = oauth2_jose_jwks_eckey_url_resolve;
739741
break;
742+
case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB:
743+
provider->jwks_uri = oauth2_uri_ctx_init(log);
744+
provider->resolve = oauth2_jose_jwks_aws_alb_resolve;
745+
provider->alb_arn = NULL;
746+
provider->alb_base_url = NULL;
747+
break;
740748
}
741749

742750
return provider;
@@ -765,6 +773,11 @@ _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->jwks_uri = oauth2_uri_ctx_clone(log, src->jwks_uri);
778+
dst->alb_arn = oauth2_strdup(src->alb_arn);
779+
dst->alb_base_url = oauth2_strdup(src->alb_base_url);
780+
break;
768781
}
769782

770783
end:
@@ -790,6 +803,14 @@ _oauth2_jose_jwks_provider_free(oauth2_log_t *log,
790803
if (provider->jwks_uri)
791804
oauth2_uri_ctx_free(log, provider->jwks_uri);
792805
break;
806+
case OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB:
807+
if (provider->jwks_uri)
808+
oauth2_uri_ctx_free(log, provider->jwks_uri);
809+
if (provider->alb_arn)
810+
oauth2_mem_free(provider->alb_arn);
811+
if (provider->alb_base_url)
812+
oauth2_mem_free(provider->alb_base_url);
813+
break;
793814
}
794815

795816
oauth2_mem_free(provider);
@@ -1292,7 +1313,7 @@ bool oauth2_jose_jwt_verify(oauth2_log_t *log,
12921313
if (jwt_verify_ctx) {
12931314

12941315
keys = jwt_verify_ctx->jwks_provider->resolve(
1295-
log, jwt_verify_ctx->jwks_provider, &refresh);
1316+
log, jwt_verify_ctx->jwks_provider, &refresh, hdr);
12961317

12971318
ctx.jws = jws;
12981319
ctx.kid = cjose_header_get(hdr, "kid", &err);
@@ -1309,7 +1330,7 @@ bool oauth2_jose_jwt_verify(oauth2_log_t *log,
13091330
if (keys)
13101331
oauth2_jose_jwk_list_free(log, keys);
13111332
keys = jwt_verify_ctx->jwks_provider->resolve(
1312-
log, jwt_verify_ctx->jwks_provider, &refresh);
1333+
log, jwt_verify_ctx->jwks_provider, &refresh, hdr);
13131334
_oauth2_jose_verification_keys_loop(
13141335
log, keys, _oauth2_jose_jwt_verify_jwk, &ctx);
13151336

@@ -1846,8 +1867,46 @@ _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri)
18461867
"eckey_uri");
18471868
}
18481869

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)
1870+
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb)
1871+
{
1872+
char *rv = NULL;
1873+
oauth2_cfg_token_verify_t *verify = (oauth2_cfg_token_verify_t *)ctx;
1874+
const char *alb_base_url = NULL;
1875+
1876+
oauth2_debug(log, "enter");
1877+
1878+
rv = _oauth2_jose_verify_options_jwk_set_url(
1879+
log, value, params, verify, OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB,
1880+
"aws_alb");
1881+
if (rv != NULL)
1882+
goto end;
1883+
1884+
oauth2_jose_jwt_verify_ctx_t *ptr = verify->ctx->ptr;
1885+
1886+
// this is going to be set dynamically
1887+
if (ptr->jwks_provider->jwks_uri->endpoint->url) {
1888+
oauth2_mem_free(ptr->jwks_provider->jwks_uri->endpoint->url);
1889+
ptr->jwks_provider->jwks_uri->endpoint->url = NULL;
1890+
}
1891+
1892+
ptr->jwks_provider->alb_arn = oauth2_strdup(value);
1893+
1894+
alb_base_url = oauth2_nv_list_get(log, params, "alb_base_url");
1895+
if (alb_base_url) {
1896+
ptr->jwks_provider->alb_base_url = oauth2_strdup(alb_base_url);
1897+
}
1898+
1899+
end:
1900+
1901+
oauth2_debug(log, "leave: %s", rv);
1902+
1903+
return rv;
1904+
}
1905+
1906+
static oauth2_jose_jwk_list_t *
1907+
oauth2_jose_jwks_list_resolve(oauth2_log_t *log,
1908+
oauth2_jose_jwks_provider_t *provider,
1909+
bool *refresh, cjose_header_t *hdr)
18511910
{
18521911
*refresh = false;
18531912
return oauth2_jose_jwk_list_clone(log, provider->jwks);
@@ -2171,22 +2230,107 @@ static oauth2_jose_jwk_list_t *_oauth2_jose_jwks_resolve_from_uri(
21712230
return dst;
21722231
}
21732232

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)
2233+
static oauth2_jose_jwk_list_t *
2234+
oauth2_jose_jwks_uri_resolve(oauth2_log_t *log,
2235+
oauth2_jose_jwks_provider_t *provider,
2236+
bool *refresh, cjose_header_t *hdr)
21762237
{
21772238
return _oauth2_jose_jwks_resolve_from_uri(
21782239
log, provider, refresh,
21792240
_oauth2_jose_jwks_uri_resolve_response_callback);
21802241
}
21812242

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)
2243+
static oauth2_jose_jwk_list_t *
2244+
oauth2_jose_jwks_eckey_url_resolve(oauth2_log_t *log,
2245+
oauth2_jose_jwks_provider_t *provider,
2246+
bool *refresh, cjose_header_t *hdr)
21842247
{
21852248
return _oauth2_jose_jwks_resolve_from_uri(
21862249
log, provider, refresh,
21872250
_oauth2_jose_jwks_eckey_url_resolve_response_callback);
21882251
}
21892252

2253+
static const char *_oauth2_jose_jwks_aws_alb_region(const char *arn)
2254+
{
2255+
if (!arn)
2256+
return NULL;
2257+
2258+
char *arn_copy = oauth2_strdup(arn);
2259+
if (!arn_copy)
2260+
return NULL;
2261+
2262+
char *token = strtok(arn_copy, ":");
2263+
int count = 0;
2264+
const char *region = NULL;
2265+
2266+
while (token) {
2267+
if (count == 3) {
2268+
region = oauth2_strdup(token);
2269+
break;
2270+
}
2271+
token = strtok(NULL, ":");
2272+
count++;
2273+
}
2274+
2275+
oauth2_mem_free(arn_copy);
2276+
return region;
2277+
}
2278+
2279+
static oauth2_jose_jwk_list_t *
2280+
oauth2_jose_jwks_aws_alb_resolve(oauth2_log_t *log,
2281+
oauth2_jose_jwks_provider_t *provider,
2282+
bool *refresh, cjose_header_t *hdr)
2283+
{
2284+
cjose_err err;
2285+
char *url = NULL;
2286+
const char *region = NULL;
2287+
2288+
const char *signer = cjose_header_get(hdr, "signer", &err);
2289+
const char *kid = cjose_header_get(hdr, "kid", &err);
2290+
2291+
if (!signer || !kid) {
2292+
oauth2_error(log,
2293+
"missing 'signer' or 'kid' in JWT header: "
2294+
"signer=%s, kid=%s",
2295+
signer, kid);
2296+
return NULL;
2297+
}
2298+
2299+
if (strcmp(signer, provider->alb_arn) != 0) {
2300+
oauth2_error(
2301+
log,
2302+
"signer does not match configured ARN: signer=%s, arn=%s",
2303+
signer, provider->alb_arn);
2304+
return NULL;
2305+
}
2306+
2307+
if (provider->alb_base_url == NULL) {
2308+
region = _oauth2_jose_jwks_aws_alb_region(provider->alb_arn);
2309+
if (!region) {
2310+
oauth2_error(
2311+
log, "failed to extract region from ARN: arn=%s",
2312+
provider->alb_arn);
2313+
return NULL;
2314+
}
2315+
url = _oauth2_stradd4(NULL, "https://public-keys.auth.elb.",
2316+
region, ".amazonaws.com/", kid);
2317+
} else {
2318+
url = oauth2_stradd(NULL, provider->alb_base_url, kid, NULL);
2319+
}
2320+
oauth2_debug(log, "constructed ALB JWKs URL: %s", url);
2321+
2322+
provider->jwks_uri->endpoint->url = url;
2323+
2324+
oauth2_jose_jwk_list_t *result = _oauth2_jose_jwks_resolve_from_uri(
2325+
log, provider, refresh,
2326+
_oauth2_jose_jwks_eckey_url_resolve_response_callback);
2327+
2328+
provider->jwks_uri->endpoint->url = NULL;
2329+
oauth2_mem_free(url);
2330+
2331+
return result;
2332+
}
2333+
21902334
/*
21912335
oauth2_jose_jwk_list_t *
21922336
oauth2_jose_jwks_resolve(oauth2_log_t *log, oauth2_cfg_token_verify_t *verify,

src/jose_int.h

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,33 @@ 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+
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;
61-
union {
62-
oauth2_uri_ctx_t *jwks_uri;
63-
oauth2_jose_jwk_list_t *jwks;
64-
};
65-
// struct oauth2_jose_jwks_provider_t *next;
63+
64+
// NB: avoid union because of compiler/memory issues
65+
66+
// OAUTH2_JOSE_JWKS_PROVIDER_JWKS_URI and
67+
// OAUTH2_JOSE_JWKS_PROVIDER_ECKEY_URI
68+
oauth2_uri_ctx_t *jwks_uri;
69+
70+
// OAUTH2_JOSE_JWKS_PROVIDER_LIST
71+
oauth2_jose_jwk_list_t *jwks;
72+
73+
// OAUTH2_JOSE_JWKS_PROVIDER_AWS_ALB
74+
char *alb_arn;
75+
char *alb_base_url;
6676
} oauth2_jose_jwks_provider_t;
6777

6878
_OAUTH2_CFG_CTX_TYPE_START(oauth2_jose_jwt_verify_ctx)
@@ -88,6 +98,7 @@ _OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_pubkey);
8898
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwk);
8999
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_jwks_uri);
90100
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_eckey_uri);
101+
_OAUTH_CFG_CTX_CALLBACK(oauth2_jose_verify_options_jwk_set_aws_alb);
91102

92103
char *oauth2_jose_resolve_from_uri(oauth2_log_t *log, oauth2_uri_ctx_t *uri_ctx,
93104
bool *refresh);

test/check_jose.c

Lines changed: 4 additions & 2 deletions
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)