Skip to content

Commit 39390f3

Browse files
authored
Merge branch 'master' into explicit_time
2 parents f22a8da + b4ac826 commit 39390f3

File tree

6 files changed

+255
-6
lines changed

6 files changed

+255
-6
lines changed

configs/export-symbols

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ global:
33
scitoken*;
44
validator*;
55
enforcer*;
6+
keycache*;
67

78
local:
89
*;

src/scitokens.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,65 @@ int enforcer_test(const Enforcer enf, const SciToken scitoken, const Acl *acl, c
480480
}
481481
return 0;
482482
}
483+
484+
485+
int keycache_refresh_jwks(const char *issuer, char **err_msg)
486+
{
487+
if (!issuer) {
488+
if (err_msg) {*err_msg = strdup("Issuer may not be a null pointer");}
489+
return -1;
490+
}
491+
try {
492+
if (!scitokens::Validator::refresh_jwks(issuer)) {
493+
if (err_msg) {*err_msg = strdup("Failed to refresh JWKS cache for issuer.");}
494+
return -1;
495+
}
496+
} catch (std::exception &exc) {
497+
if (err_msg) {*err_msg = strdup(exc.what());}
498+
return -1;
499+
}
500+
return 0;
501+
}
502+
503+
504+
int keycache_get_cached_jwks(const char *issuer, char **jwks, char **err_msg)
505+
{
506+
if (!issuer) {
507+
if (err_msg) {*err_msg = strdup("Issuer may not be a null pointer");}
508+
return -1;
509+
}
510+
if (!jwks) {
511+
if (err_msg) {*err_msg = strdup("JWKS output pointer may not be null.");}
512+
return -1;
513+
}
514+
try {
515+
*jwks = strdup(scitokens::Validator::get_jwks(issuer).c_str());
516+
} catch(std::exception &exc) {
517+
if (err_msg) {*err_msg = strdup(exc.what());}
518+
return -1;
519+
}
520+
return 0;
521+
}
522+
523+
524+
int keycache_set_jwks(const char *issuer, const char *jwks, char **err_msg)
525+
{
526+
if (!issuer) {
527+
if (err_msg) {*err_msg = strdup("Issuer may not be a null pointer");}
528+
return -1;
529+
}
530+
if (!jwks) {
531+
if (err_msg) {*err_msg = strdup("JWKS pointer may not be null.");}
532+
return -1;
533+
}
534+
try {
535+
if (!scitokens::Validator::store_jwks(issuer, jwks)) {
536+
if (err_msg) {*err_msg = strdup("Failed to set the JWKS cache for issuer.");}
537+
return -1;
538+
}
539+
} catch(std::exception &exc) {
540+
if (err_msg) {*err_msg = strdup(exc.what());}
541+
return -1;
542+
}
543+
return 0;
544+
}

src/scitokens.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,39 @@ void enforcer_acl_free(Acl *acls);
142142

143143
int enforcer_test(const Enforcer enf, const SciToken sci, const Acl *acl, char **err_msg);
144144

145+
146+
/**
147+
* API for explicity managing the key cache.
148+
*
149+
* This manipulates the keycache for the current eUID.
150+
*/
151+
152+
153+
/**
154+
* Refresh the JWKS in the keycache for a given issuer; the refresh will occur
155+
* even if the JWKS is not otherwise due for updates.
156+
* - Returns 0 on success, nonzero on failure.
157+
*/
158+
int keycache_refresh_jwks(const char *issuer, char **err_msg);
159+
160+
/**
161+
* Retrieve the JWKS from the keycache for a given issuer.
162+
* - Returns 0 if successful, nonzero on failure.
163+
* - If the existing JWKS has expired - or does not exist - this does not trigger a new
164+
* download of the JWKS from the issuer. Instead, it will return a JWKS object with
165+
* an empty set of keys.
166+
* - `jwks` is an output variable set to the contents of the JWKS in the key cache.
167+
*/
168+
int keycache_get_cached_jwks(const char *issuer, char **jwks, char **err_msg);
169+
170+
/**
171+
* Replace any existing key cache entry with one provided by the user.
172+
* The expiration and next update time of the user-provided JWKS will utilize
173+
* the same rules as a download from an issuer with no explicit cache lifetime directives.
174+
* - `jwks` is value that will be set in the cache.
175+
*/
176+
int keycache_set_jwks(const char *issuer, const char *jwks, char **err_msg);
177+
145178
#ifdef __cplusplus
146179
}
147180
#endif

src/scitokens_internal.cpp

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,12 @@ normalize_absolute_path(const std::string &path) {
439439
}
440440

441441

442+
void get_default_expiry_time(int &next_update_delta, int &expiry_delta)
443+
{
444+
next_update_delta = 600;
445+
expiry_delta = 4*24*3600;
446+
}
447+
442448
}
443449

444450

@@ -511,8 +517,49 @@ Validator::get_public_keys_from_web(const std::string &issuer, picojson::value &
511517

512518
keys = json_obj;
513519

514-
next_update = now + 600;
515-
expires = now + 4*24*3600;
520+
int next_update_delta, expiry_delta;
521+
get_default_expiry_time(next_update_delta, expiry_delta);
522+
next_update = now + next_update_delta;
523+
expires = now + expiry_delta;
524+
}
525+
526+
527+
std::string
528+
Validator::get_jwks(const std::string &issuer)
529+
{
530+
auto now = std::time(NULL);
531+
picojson::value jwks;
532+
int64_t next_update;
533+
if (get_public_keys_from_db(issuer, now, jwks, next_update)) {
534+
return jwks.serialize();
535+
}
536+
return std::string("{\"keys\": []}");
537+
}
538+
539+
540+
bool
541+
Validator::refresh_jwks(const std::string &issuer)
542+
{
543+
int64_t next_update, expires;
544+
picojson::value keys;
545+
get_public_keys_from_web(issuer, keys, next_update, expires);
546+
return store_public_keys(issuer, keys, next_update, expires);
547+
}
548+
549+
550+
bool
551+
Validator::store_jwks(const std::string &issuer, const std::string &jwks_str)
552+
{
553+
picojson::value jwks;
554+
std::string err = picojson::parse(jwks, jwks_str);
555+
auto now = std::time(NULL);
556+
int next_update_delta, expiry_delta;
557+
get_default_expiry_time(next_update_delta, expiry_delta);
558+
int64_t next_update = now + next_update_delta, expires = now + expiry_delta;
559+
if (!err.empty()) {
560+
throw JsonException(err);
561+
}
562+
return store_public_keys(issuer, jwks, next_update, expires);
516563
}
517564

518565
void
@@ -691,7 +738,9 @@ scitokens::Validator::store_public_ec_key(const std::string &issuer, const std::
691738
picojson::value top_value(top_obj);
692739

693740
auto now = std::time(NULL);
694-
return store_public_keys(issuer, top_value, now + 600, now + 4*3600);
741+
int next_update_delta, expiry_delta;
742+
get_default_expiry_time(next_update_delta, expiry_delta);
743+
return store_public_keys(issuer, top_value, now + next_update_delta, now + expiry_delta);
695744
}
696745

697746

src/scitokens_internal.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,10 +530,26 @@ class Validator {
530530
*/
531531
static bool store_public_ec_key(const std::string &issuer, const std::string &kid, const std::string &key);
532532

533+
/**
534+
* Store the contents of a JWKS for a given issuer.
535+
*/
536+
static bool store_jwks(const std::string &issuer, const std::string &jwks);
537+
538+
/**
539+
* Trigger a refresh of the JWKS or a given issuer.
540+
*/
541+
static bool refresh_jwks(const std::string &issuer);
542+
543+
/**
544+
* Fetch the contents of fa JWKS for a given issuer (do not trigger a refresh).
545+
* Will return an empty JWKS if no valid JWKS is available.
546+
*/
547+
static std::string get_jwks(const std::string &issuer);
548+
533549
private:
534550
void get_public_key_pem(const std::string &issuer, const std::string &kid, std::string &public_pem, std::string &algorithm);
535-
void get_public_keys_from_web(const std::string &issuer, picojson::value &keys, int64_t &next_update, int64_t &expires);
536-
bool get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update);
551+
static void get_public_keys_from_web(const std::string &issuer, picojson::value &keys, int64_t &next_update, int64_t &expires);
552+
static bool get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update);
537553
static bool store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires);
538554

539555
bool m_validate_all_claims{true};

test/main.cpp

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "../src/scitokens.h"
22

33
#include <memory>
4-
54
#include <gtest/gtest.h>
65

76
namespace {
@@ -60,6 +59,95 @@ TEST(SciTokenTest, SignToken) {
6059
}
6160

6261

62+
class KeycacheTest : public ::testing::Test
63+
{
64+
protected:
65+
std::string demo_scitokens_url = "https://demo.scitokens.org";
66+
67+
void SetUp() override {
68+
char *err_msg;
69+
auto rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens.c_str(), &err_msg);
70+
ASSERT_TRUE(rv == 0);
71+
}
72+
73+
// Reference copy of the keys at https://demo.scitokens.org/oauth2/certs; may need
74+
// to be updated periodically.
75+
std::string demo_scitokens = "{\"keys\":[{\"alg\":\"RS256\",\"e\":\"AQAB\",\"kid\":\"key-rs256\",\"kty\":\"RSA\",\"n\":\"uGDGTLXnqh3mfopjys6sFUBvFl3F4Qt6NEYphq_u_aBhtN1X9NEyb78uB_I1KjciJNGLIQU0ECsJiFx6qV1hR9xE1dPyrS3bU92AVtnBrvzUtTU-aUZAmZQiuAC_rC0-z_TOQr6qJkkUgZtxR9n9op55ZBpRfZD5dzhkW4Dm146vfTKt0D4cIMoMNJS5xQx9nibeB4E8hryZDW_fPeD0XZDcpByNyP0jFDYkxdUtQFvyRpz4WMZ4ejUfvW3gf4LRAfGZJtMnsZ7ZW4RfoQbhiXKMfWeBEjQDiXh0r-KuZLykxhYJtpf7fTnPna753IzMgRMmW3F69iQn2LQN3LoSMw==\",\"use\":\"sig\"},{\"alg\":\"ES256\",\"kid\":\"key-es256\",\"kty\":\"EC\",\"use\":\"sig\",\"x\":\"ncSCrGTBTXXOhNiAOTwNdPjwRz1hVY4saDNiHQK9Bh4=\",\"y\":\"sCsFXvx7FAAklwq3CzRCBcghqZOFPB2dKUayS6LY_Lo=\"}]}";
76+
std::string demo_scitokens2 = "{\"keys\":[{\"alg\":\"ES256\",\"kid\":\"key-es256\",\"kty\":\"EC\",\"use\":\"sig\",\"x\":\"ncSCrGTBTXXOhNiAOTwNdPjwRz1hVY4saDNiHQK9Bh4=\",\"y\":\"sCsFXvx7FAAklwq3CzRCBcghqZOFPB2dKUayS6LY_Lo=\"}]}";
77+
};
78+
79+
80+
TEST_F(KeycacheTest, RefreshTest) {
81+
char *err_msg;
82+
auto rv = keycache_refresh_jwks(demo_scitokens_url.c_str(), &err_msg);
83+
ASSERT_TRUE(rv == 0);
84+
85+
char *output_jwks;
86+
rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &output_jwks, &err_msg);
87+
ASSERT_TRUE(rv == 0);
88+
ASSERT_TRUE(output_jwks != nullptr);
89+
std::string output_jwks_str(output_jwks);
90+
free(output_jwks);
91+
92+
EXPECT_EQ(demo_scitokens, output_jwks_str);
93+
}
94+
95+
96+
TEST_F(KeycacheTest, RefreshInvalid)
97+
{
98+
char *err_msg, *jwks;
99+
auto rv = keycache_refresh_jwks("https://demo.scitokens.org/invalid", &err_msg);
100+
ASSERT_FALSE(rv == 0);
101+
102+
rv = keycache_get_cached_jwks("https://demo.scitokens.org/invalid", &jwks, &err_msg);
103+
ASSERT_TRUE(rv == 0);
104+
ASSERT_TRUE(jwks != nullptr);
105+
std::string jwks_str(jwks);
106+
free(jwks);
107+
108+
EXPECT_EQ(jwks_str, "{\"keys\": []}");
109+
}
110+
111+
112+
TEST_F(KeycacheTest, GetInvalid)
113+
{
114+
char *err_msg, *jwks;
115+
auto rv = keycache_get_cached_jwks("https://demo.scitokens.org/unknown", &jwks, &err_msg);
116+
ASSERT_TRUE(rv == 0);
117+
ASSERT_TRUE(jwks != nullptr);
118+
std::string jwks_str(jwks);
119+
free(jwks);
120+
}
121+
122+
123+
TEST_F(KeycacheTest, GetTest) {
124+
char *err_msg, *jwks;
125+
auto rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg);
126+
ASSERT_TRUE(rv == 0);
127+
ASSERT_TRUE(jwks != nullptr);
128+
std::string jwks_str(jwks);
129+
free(jwks);
130+
131+
EXPECT_EQ(demo_scitokens, jwks_str);
132+
}
133+
134+
135+
TEST_F(KeycacheTest, SetGetTest) {
136+
char *err_msg;
137+
auto rv = keycache_set_jwks(demo_scitokens_url.c_str(), demo_scitokens2.c_str(), &err_msg);
138+
ASSERT_TRUE(rv == 0);
139+
140+
char *jwks;
141+
rv = keycache_get_cached_jwks(demo_scitokens_url.c_str(), &jwks, &err_msg);
142+
ASSERT_TRUE(rv == 0);
143+
ASSERT_TRUE(jwks != nullptr);
144+
std::string jwks_str(jwks);
145+
free(jwks);
146+
147+
EXPECT_EQ(demo_scitokens2, jwks_str);
148+
}
149+
150+
63151
class SerializeTest : public ::testing::Test {
64152
protected:
65153
void SetUp() override {

0 commit comments

Comments
 (0)