Skip to content

Commit 8226e19

Browse files
authored
Merge pull request #73 from olifre/atjwt
Add support for at+jwt profile.
2 parents 79ea377 + febbee7 commit 8226e19

File tree

5 files changed

+85
-7
lines changed

5 files changed

+85
-7
lines changed

src/create.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const char usage[] = \
2323
" -k | --key <key_file> File containing the signing private key.\n"
2424
" -K | --keyid <kid> Name of the token key.\n"
2525
" -i | --issuer <issuer> Issuer for the token.\n"
26-
" -p | --profile <profile> Token profile (wlcg, scitokens1, scitokens2).\n"
26+
" -p | --profile <profile> Token profile (wlcg, scitokens1, scitokens2, atjwt).\n"
2727
"\n";
2828

2929
const struct option long_options[] =
@@ -180,6 +180,8 @@ int main(int argc, char *argv[]) {
180180
profile = SciTokenProfile::SCITOKENS_1_0;
181181
} else if (g_profile == "scitokens2") {
182182
profile = SciTokenProfile::SCITOKENS_2_0;
183+
} else if (g_profile == "atjwt") {
184+
profile = SciTokenProfile::AT_JWT;
183185
} else {
184186
fprintf(stderr, "Unknown token profile: %s\n", g_profile.c_str());
185187
return 1;

src/scitokens.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ Acl;
2525
* - COMPAT mode (default) indicates any supported token format
2626
* is acceptable. Where possible, the scope names are translated into
2727
* equivalent SciTokens 1.0 claim names (i.e., storage.read -> read; storage.write -> write).
28-
* - SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0: only accept these specific profiles.
28+
* If a typ header claim is present, use that to deduce type (RFC8725 Section 3.11).
29+
* - SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0, AT_JWT: only accept these specific profiles.
2930
* No automatic translation is performed.
3031
*/
3132
typedef enum _profile {
3233
COMPAT = 0,
3334
SCITOKENS_1_0,
3435
SCITOKENS_2_0,
35-
WLCG_1_0
36+
WLCG_1_0,
37+
AT_JWT
3638
} SciTokenProfile;
3739

3840
SciTokenKey scitoken_key_create(const char *key_id, const char *algorithm, const char *public_contents, const char *private_contents, char **err_msg);

src/scitokens_internal.h

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ friend class scitokens::Validator;
116116
COMPAT = 0,
117117
SCITOKENS_1_0,
118118
SCITOKENS_2_0,
119-
WLCG_1_0
119+
WLCG_1_0,
120+
AT_JWT
120121
};
121122

122123
SciToken(SciTokenKey &signing_algorithm)
@@ -198,6 +199,9 @@ friend class scitokens::Validator;
198199
builder.set_issued_at(time);
199200
builder.set_not_before(time);
200201
builder.set_expires_at(time + std::chrono::seconds(m_lifetime));
202+
if (m_serialize_profile == Profile::AT_JWT) {
203+
builder.set_type("at+jwt");
204+
}
201205

202206
uuid_t uuid;
203207
uuid_generate(uuid);
@@ -258,11 +262,31 @@ class Validator {
258262
}
259263

260264
void verify(const jwt::decoded_jwt &jwt) {
265+
// If token has a typ header claim (RFC8725 Section 3.11), trust that in COMPAT mode.
266+
if (jwt.has_type()) {
267+
std::string t_type = jwt.get_type();
268+
if (m_validate_profile == SciToken::Profile::COMPAT) {
269+
if (t_type == "at+jwt" || t_type == "application/at+jwt") {
270+
m_profile = SciToken::Profile::AT_JWT;
271+
}
272+
} else if (m_validate_profile == SciToken::Profile::AT_JWT) {
273+
if (t_type != "at+jwt" && t_type != "application/at+jwt") {
274+
throw jwt::token_verification_exception("'typ' header claim must be at+jwt");
275+
}
276+
m_profile = SciToken::Profile::AT_JWT;
277+
}
278+
} else {
279+
if (m_validate_profile == SciToken::Profile::AT_JWT) {
280+
throw jwt::token_verification_exception("'typ' header claim must be set for at+jwt tokens");
281+
}
282+
}
261283
if (!jwt.has_payload_claim("iat")) {
262284
throw jwt::token_verification_exception("'iat' claim is mandatory");
263285
}
264-
if (!jwt.has_payload_claim("nbf")) {
265-
throw jwt::token_verification_exception("'nbf' claim is mandatory");
286+
if (m_profile != SciToken::Profile::AT_JWT) {
287+
if (!jwt.has_payload_claim("nbf")) {
288+
throw jwt::token_verification_exception("'nbf' claim is mandatory");
289+
}
266290
}
267291
if (!jwt.has_payload_claim("exp")) {
268292
throw jwt::token_verification_exception("'exp' claim is mandatory");
@@ -365,6 +389,9 @@ class Validator {
365389
if (!jwt.has_payload_claim("aud")) {
366390
throw jwt::token_verification_exception("Malformed token: 'aud' claim required for WLCG profile");
367391
}
392+
} else if (m_profile == SciToken::Profile::AT_JWT) {
393+
// detected early above from typ header claim.
394+
must_verify_everything = false;
368395
} else {
369396
if ((m_validate_profile != SciToken::Profile::COMPAT) &&
370397
(m_validate_profile != SciToken::Profile::SCITOKENS_1_0))

src/verify.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const char usage[] = \
1616
" -c | --cred <cred_file> File containing the signing credential.\n"
1717
" -i | --issuer <issuer> Issuer of the token to verify.\n"
1818
" -K | --keyid <kid> Name of the token key.\n"
19-
" -p | --profile <profile> Profile to enforce (wlcg, scitokens1, scitokens2).\n"
19+
" -p | --profile <profile> Profile to enforce (wlcg, scitokens1, scitokens2, atjwt).\n"
2020
"\n";
2121

2222
const struct option long_options[] =

test/main.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,53 @@ TEST_F(SerializeTest, FailVerifyToken) {
207207
EXPECT_FALSE(rv == 0);
208208
}
209209

210+
TEST_F(SerializeTest, VerifyATJWTTest) {
211+
212+
char *err_msg = nullptr;
213+
214+
// Serialize as at+jwt token.
215+
char *token_value = nullptr;
216+
scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::AT_JWT);
217+
auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg);
218+
ASSERT_TRUE(rv == 0);
219+
std::unique_ptr<char, decltype(&free)> token_value_ptr(token_value, free);
220+
221+
// Accepts any profile.
222+
rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg);
223+
ASSERT_TRUE(rv == 0);
224+
225+
// Accepts only an at+jwt token, should work with at+jwt token
226+
scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::AT_JWT);
227+
rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg);
228+
ASSERT_TRUE(rv == 0);
229+
230+
// Accepts only SciToken 2.0; should fail
231+
scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::SCITOKENS_2_0);
232+
rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg);
233+
ASSERT_FALSE(rv == 0);
234+
}
235+
236+
TEST_F(SerializeTest, FailVerifyATJWTTest) {
237+
238+
char *err_msg = nullptr;
239+
240+
// Serialize as "compat" token.
241+
char *token_value = nullptr;
242+
scitoken_set_serialize_profile(m_token.get(), SciTokenProfile::COMPAT);
243+
auto rv = scitoken_serialize(m_token.get(), &token_value, &err_msg);
244+
ASSERT_TRUE(rv == 0);
245+
std::unique_ptr<char, decltype(&free)> token_value_ptr(token_value, free);
246+
247+
// Accepts any profile.
248+
rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg);
249+
ASSERT_TRUE(rv == 0);
250+
251+
// Accepts only an at+jwt token, should fail with COMPAT token
252+
scitoken_set_deserialize_profile(m_read_token.get(), SciTokenProfile::AT_JWT);
253+
rv = scitoken_deserialize_v2(token_value, m_read_token.get(), nullptr, &err_msg);
254+
ASSERT_FALSE(rv == 0);
255+
}
256+
210257
}
211258

212259
int main(int argc, char **argv) {

0 commit comments

Comments
 (0)