Attempt to download updated JWKs if the client presents an unknown key.

Limit the download of keys to every 900s.

Resolves: #284
Signed-off-by: Alan Jowett <alanjo@microsoft.com>
This commit is contained in:
Alan Jowett
2020-04-14 10:10:19 -06:00
parent df3b925524
commit 9d9907ef5e
3 changed files with 78 additions and 7 deletions

View File

@@ -35,9 +35,14 @@
#include <cjose/cjose.h> #include <cjose/cjose.h>
#include <time.h> #include <time.h>
#define MINIMUM_KEY_REFRESH_INTERVAL (900)
typedef struct oidc_vctx_st { typedef struct oidc_vctx_st {
json_t *config; json_t *config;
json_t *jwks; json_t *jwks;
void * pool;
int minimum_jwk_refresh_time;
time_t last_jwks_load_time;
} oidc_vctx_st; } oidc_vctx_st;
typedef struct oidc_ctx_st { typedef struct oidc_ctx_st {
@@ -46,7 +51,7 @@ typedef struct oidc_ctx_st {
int token_verified; int token_verified;
} oidc_ctx_st; } oidc_ctx_st;
static bool oidc_fetch_oidc_keys(void * pool, oidc_vctx_st * vctx); static bool oidc_fetch_oidc_keys(oidc_vctx_st * vctx);
static bool oidc_verify_token(oidc_vctx_st * vctx, const char *token, static bool oidc_verify_token(oidc_vctx_st * vctx, const char *token,
size_t token_length, size_t token_length,
char user_name[MAX_USERNAME_SIZE]); char user_name[MAX_USERNAME_SIZE]);
@@ -64,6 +69,7 @@ static void oidc_vhost_init(void **vctx, void *pool, void *additional)
} }
vc->config = NULL; vc->config = NULL;
vc->jwks = NULL; vc->jwks = NULL;
vc->pool = pool;
if (config == NULL) { if (config == NULL) {
syslog(LOG_ERR, "ocserv-oidc: no configuration passed!\n"); syslog(LOG_ERR, "ocserv-oidc: no configuration passed!\n");
@@ -94,7 +100,13 @@ static void oidc_vhost_init(void **vctx, void *pool, void *additional)
exit(1); exit(1);
} }
if (!oidc_fetch_oidc_keys(pool, vc)) { if (json_object_get(vc->config, "minimum_jwk_refresh_time")) {
vc->minimum_jwk_refresh_time = json_integer_value(json_object_get(vc->config, "minimum_jwk_refresh_time"));
} else {
vc->minimum_jwk_refresh_time = MINIMUM_KEY_REFRESH_INTERVAL;
}
if (!oidc_fetch_oidc_keys(vc)) {
syslog(LOG_ERR, "ocserv-oidc: failed to load jwks\n"); syslog(LOG_ERR, "ocserv-oidc: failed to load jwks\n");
exit(1); exit(1);
} }
@@ -303,13 +315,17 @@ static json_t *oidc_fetch_json_from_uri(void * pool, const char *uri)
} }
// Download and parse the JWT keys for this virtual server context // Download and parse the JWT keys for this virtual server context
static bool oidc_fetch_oidc_keys(void * pool, oidc_vctx_st * vctx) static bool oidc_fetch_oidc_keys(oidc_vctx_st * vctx)
{ {
bool result = false; bool result = false;
json_t *jwks = NULL; json_t *jwks = NULL;
json_t *openid_configuration_url = json_t *openid_configuration_url =
json_object_get(vctx->config, "openid_configuration_url"); json_object_get(vctx->config, "openid_configuration_url");
json_t *array;
size_t index;
json_t *value;
if (!openid_configuration_url) { if (!openid_configuration_url) {
syslog(LOG_AUTH, syslog(LOG_AUTH,
"ocserv-oidc: openid_configuration_url missing from config\n"); "ocserv-oidc: openid_configuration_url missing from config\n");
@@ -317,7 +333,7 @@ static bool oidc_fetch_oidc_keys(void * pool, oidc_vctx_st * vctx)
} }
json_t *oidc_config = json_t *oidc_config =
oidc_fetch_json_from_uri(pool, oidc_fetch_json_from_uri(vctx->pool,
json_string_value json_string_value
(openid_configuration_url)); (openid_configuration_url));
@@ -334,7 +350,7 @@ static bool oidc_fetch_oidc_keys(void * pool, oidc_vctx_st * vctx)
goto cleanup; goto cleanup;
} }
jwks = oidc_fetch_json_from_uri(pool, json_string_value(jwks_uri)); jwks = oidc_fetch_json_from_uri(vctx->pool, json_string_value(jwks_uri));
if (!jwks) { if (!jwks) {
syslog(LOG_AUTH, syslog(LOG_AUTH,
"ocserv-oidc: failed to fetch keys from jwks_uri %s\n", "ocserv-oidc: failed to fetch keys from jwks_uri %s\n",
@@ -342,10 +358,27 @@ static bool oidc_fetch_oidc_keys(void * pool, oidc_vctx_st * vctx)
goto cleanup; goto cleanup;
} }
array = json_object_get(jwks, "keys");
if (array == NULL) {
syslog(LOG_AUTH, "ocserv-oidc: JWK keys malformed\n");
goto cleanup;
}
// Log the keys obtained
json_array_foreach(array, index, value) {
json_t *key_kid = json_object_get(value, "kid");
syslog(LOG_INFO,
"ocserv-oidc: fetched new JWK %s\n",
json_string_value(key_kid)
);
}
if (vctx->jwks) { if (vctx->jwks) {
json_decref(vctx->jwks); json_decref(vctx->jwks);
} }
vctx->last_jwks_load_time = time(0);
vctx->jwks = jwks; vctx->jwks = jwks;
jwks = NULL; jwks = NULL;
result = true; result = true;
@@ -537,8 +570,20 @@ static bool oidc_verify_singature(oidc_vctx_st * vctx, cjose_jws_t * jws)
} }
if (jwk == NULL) { if (jwk == NULL) {
time_t now;
syslog(LOG_AUTH, "ocserv-oidc: JWK with kid=%s not found\n", syslog(LOG_AUTH, "ocserv-oidc: JWK with kid=%s not found\n",
json_string_value(token_kid)); json_string_value(token_kid));
syslog(LOG_AUTH, "ocserv-oidc: attempting to download new JWKs");
now = time(0);
if ((now - vctx->last_jwks_load_time) > vctx->minimum_jwk_refresh_time) {
oidc_fetch_oidc_keys(vctx);
}
else {
syslog(LOG_AUTH, "ocserv-oidc: skipping JWK refresh");
}
// Fail the request and let the client try again.
goto cleanup; goto cleanup;
} }

View File

@@ -65,6 +65,10 @@ json_t *create_oidc_config(const char *openid_configuration_url,
goto cleanup; goto cleanup;
} }
if (json_object_set_new(config, "minimum_jwk_refresh_time", json_integer(0))) {
goto cleanup;
}
required_claims = NULL; required_claims = NULL;
result = true; result = true;
@@ -257,19 +261,21 @@ int main(int argc, char **argv)
const char audience[] = "SomeAudience"; const char audience[] = "SomeAudience";
const char issuer[] = "SomeIssuer"; const char issuer[] = "SomeIssuer";
const char user_name_claim[] = "preferred_user_name"; const char user_name_claim[] = "preferred_user_name";
const char kid[] = "My Fake Key"; char kid[64];
const char user_name[] = "SomeUser"; const char user_name[] = "SomeUser";
const char typ[] = "JWT"; const char typ[] = "JWT";
const char alg[] = "ES256"; const char alg[] = "ES256";
time_t now = time(NULL); time_t now = time(NULL);
snprintf(kid, sizeof(kid), "key_%ld", now);
if (!getcwd(working_directory, sizeof(working_directory))) { if (!getcwd(working_directory, sizeof(working_directory))) {
return 1; return 1;
} }
strncat(working_directory, "/data", sizeof(working_directory)-1); strncat(working_directory, "/data", sizeof(working_directory)-1);
working_directory[sizeof(working_directory)-1] = 0; working_directory[sizeof(working_directory)-1] = 0;
cjose_jwk_t *key = create_key("My Fake Key"); cjose_jwk_t *key = create_key(kid);
generate_config_files(working_directory, key, audience, issuer, generate_config_files(working_directory, key, audience, issuer,
user_name_claim); user_name_claim);

View File

@@ -46,6 +46,26 @@ for token in data/fail_*; do
fi fi
done done
sleep 5s
# Generate new OIDC keys
# First client should fail, triggering reload of keys
`dirname $0`/gen_oidc_test_data
for token in data/success_*; do
http_result=$(LD_PRELOAD=libsocket_wrapper.so curl --insecure https://$ADDRESS:$PORT --request POST --data config-auth.xml --header "Authorization:Bearer=`cat $token`" --output /dev/null --write-out "%{http_code}")
if [ "$http_result" != "401" ]; then
fail $PID "Token incorrectly accepted returned $http_result"
fi
done
# Second client should succeed with new keys
for token in data/success_*; do
http_result=$(LD_PRELOAD=libsocket_wrapper.so curl --insecure https://$ADDRESS:$PORT --request POST --data config-auth.xml --header "Authorization:Bearer=`cat $token`" --output /dev/null --write-out "%{http_code}")
if [ "$http_result" != "200" ]; then
fail $PID "Token incorrectly rejected returned $http_result"
fi
done
if ! test -f ${PIDFILE};then if ! test -f ${PIDFILE};then
fail $PID "Could not find pid file ${PIDFILE}" fail $PID "Could not find pid file ${PIDFILE}"
fi fi