Skip to content

Commit 95f8001

Browse files
committed
Add support for scitokens verifiers.
1 parent 150f5b8 commit 95f8001

File tree

6 files changed

+209
-11
lines changed

6 files changed

+209
-11
lines changed

src/scitokens.cpp

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ int scitoken_serialize(const SciToken token, char **value, char **err_msg) {
9999
return 0;
100100
}
101101

102-
int scitoken_deserialize(const char *value, SciToken *token, char **err_msg) {
102+
int scitoken_deserialize(const char *value, SciToken *token, char **allowed_issuers, char **err_msg) {
103103
if (value == nullptr) {
104104
if (err_msg) {*err_msg = strdup("Token may not be NULL");}
105105
return -1;
@@ -112,8 +112,15 @@ int scitoken_deserialize(const char *value, SciToken *token, char **err_msg) {
112112
scitokens::SciTokenKey key;
113113
scitokens::SciToken *real_token = new scitokens::SciToken(key);
114114

115+
std::vector<std::string> allowed_issuers_vec;
116+
if (allowed_issuers != nullptr) {
117+
for (int idx=0; allowed_issuers[idx]; idx++) {
118+
allowed_issuers_vec.push_back(allowed_issuers[idx]);
119+
}
120+
}
121+
115122
try {
116-
real_token->deserialize(value);
123+
real_token->deserialize(value, allowed_issuers_vec);
117124
} catch (std::exception &exc) {
118125
if (err_msg) {
119126
*err_msg = strdup(exc.what());
@@ -125,13 +132,49 @@ int scitoken_deserialize(const char *value, SciToken *token, char **err_msg) {
125132
}
126133

127134
Validator validator_create() {
128-
return nullptr;
135+
return new Validator();
129136
}
130137

131-
int validator_add(ValidatorFunction validator_func) {
132-
return -1;
138+
int validator_add(Validator validator, const char *claim, ValidatorFunction validator_func, char **err_msg) {
139+
if (validator == nullptr) {
140+
if (err_msg) {*err_msg = strdup("Validator may not be a null pointer");}
141+
return -1;
142+
}
143+
auto real_validator = reinterpret_cast<scitokens::Validator*>(validator);
144+
if (claim == nullptr) {
145+
if (err_msg) {*err_msg = strdup("Claim name may not be a null pointer");}
146+
return -1;
147+
}
148+
if (validator_func == nullptr) {
149+
if (err_msg) {*err_msg = strdup("Validator function may not be a null pointer");}
150+
return -1;
151+
}
152+
real_validator->add_string_validator(claim, validator_func);
153+
return 0;
133154
}
134155

156+
int validator_add_critical_claims(Validator validator, const char **claims, char **err_msg) {
157+
if (validator == nullptr) {
158+
if (err_msg) {*err_msg = strdup("Validator may not be a null pointer");}
159+
return -1;
160+
}
161+
auto real_validator = reinterpret_cast<scitokens::Validator*>(validator);
162+
if (claims == nullptr) {
163+
if (err_msg) {*err_msg = strdup("Claim list may not be a null pointer");}
164+
return -1;
165+
}
166+
std::vector<std::string> claims_vec;
167+
for (int idx=0; claims[idx]; idx++) {
168+
claims_vec.push_back(claims[idx]);
169+
}
170+
real_validator->add_critical_claims(claims_vec);
171+
return 0;
172+
}
173+
174+
175+
int validator_validate(Validator validator, SciToken scitoken, char **err_msg);
176+
177+
135178
Enforcer enforcer(const char *issuer, const char **audience) {
136179
return nullptr;
137180
}

src/scitokens.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ void scitoken_set_lifetime(SciToken token, int lifetime);
3636

3737
int scitoken_serialize(const SciToken token, char **value, char **err_msg);
3838

39-
int scitoken_deserialize(const char *value, SciToken *token, char **err_msg);
39+
int scitoken_deserialize(const char *value, SciToken *token, char **allowed_issuers, char **err_msg);
4040

4141
Validator validator_create();
4242

43-
int validator_add(ValidatorFunction validator_func);
43+
int validator_add(Validator validator, const char *claim, ValidatorFunction validator_func, char **err_msg);
44+
45+
int validator_add_critical_claims(Validator validator, const char **claims, char **err_msg);
46+
47+
int validator_validate(Validator validator, SciToken scitoken, char **err_msg);
4448

4549
Enforcer enforcer(const char *issuer, const char **audience);
4650

src/scitokens_internal.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
#include <memory>
3+
#include <sstream>
34

45
#include <curl/curl.h>
56
#include <jwt-cpp/base.h>
@@ -259,13 +260,61 @@ rs256_from_coords(const std::string &e_str, const std::string &n_str) {
259260
return result;
260261
}
261262

263+
264+
/**
265+
* Normalize path: collapse etc.
266+
* >>> normalize_path('/a/b///c')
267+
* '/a/b/c'
268+
*/
269+
std::string
270+
normalize_absolute_path(const std::string &path) {
271+
if ((path == "//") || (path == "/") || (path == "")) {
272+
return "/";
273+
}
274+
std::vector<std::string> path_components;
275+
auto path_iter = path.begin();
276+
while (path_iter != path.end()) {
277+
while (*path_iter == '/') {path_iter++;}
278+
auto next_path_iter = std::find(path_iter, path.end(), '/');
279+
std::string component;
280+
component.reserve(std::distance(path_iter, next_path_iter));
281+
component.assign(path_iter, next_path_iter);
282+
path_components.push_back(component);
283+
path_iter = next_path_iter;
284+
}
285+
std::vector<std::string> path_components_filtered;
286+
path_components_filtered.reserve(path_components.size());
287+
for (const auto &component : path_components) {
288+
if (component == "..") {
289+
path_components_filtered.pop_back();
290+
} else if (!component.empty() && component != ".") {
291+
path_components_filtered.push_back(component);
292+
}
293+
}
294+
std::stringstream ss;
295+
for (const auto &component : path_components_filtered) {
296+
ss << "/" << component;
297+
}
298+
std::string result = ss.str();
299+
return result.empty() ? "/" : result;
262300
}
263301

302+
303+
int empty_validator(const char *, char **) {
304+
return 0;
305+
}
306+
307+
308+
}
309+
310+
264311
void
265-
SciToken::deserialize(const std::string &data) {
312+
SciToken::deserialize(const std::string &data, const std::vector<std::string> allowed_issuers) {
266313
m_decoded.reset(new jwt::decoded_jwt(data));
267314

268315
scitokens::Validator val;
316+
val.add_allowed_issuers(allowed_issuers);
317+
val.set_validate_all_claims_scitokens_1(false);
269318
val.verify(*m_decoded);
270319
}
271320

src/scitokens_internal.h

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
#include <memory>
3+
#include <sstream>
34

45
#include <jwt-cpp/jwt.h>
56

@@ -133,7 +134,7 @@ class SciToken {
133134
}
134135

135136
void
136-
deserialize(const std::string &data);
137+
deserialize(const std::string &data, std::vector<std::string> allowed_issuers={});
137138

138139
private:
139140
bool m_issuer_set{false};
@@ -145,6 +146,9 @@ class SciToken {
145146

146147
class Validator {
147148

149+
typedef int (*ValidatorFunction)(const char *value, char **err_msg);
150+
typedef std::map<std::string, std::vector<ValidatorFunction>> ClaimValidatorMap;
151+
148152
public:
149153
void verify(jwt::decoded_jwt &jwt) {
150154
if (!jwt.has_payload_claim("iat")) {
@@ -162,6 +166,27 @@ class Validator {
162166
if (!jwt.has_header_claim("kid")) {
163167
throw jwt::token_verification_exception("'kid' claim is mandatory");
164168
}
169+
if (!m_allowed_issuers.empty()) {
170+
std::string issuer = jwt.get_issuer();
171+
bool permitted = false;
172+
for (const auto &allowed_issuer : m_allowed_issuers) {
173+
if (issuer == allowed_issuer) {
174+
permitted = true;
175+
break;
176+
}
177+
}
178+
if (!permitted) {
179+
throw jwt::token_verification_exception("Token issuer is not in list of allowed issuers.");
180+
}
181+
}
182+
183+
for (const auto &claim : m_critical_claims) {
184+
if (!jwt.has_payload_claim(claim)) {
185+
std::stringstream ss;
186+
ss << "'" << claim << "' claim is mandatory";
187+
throw jwt::token_verification_exception(ss.str());
188+
}
189+
}
165190

166191
std::string public_pem;
167192
std::string algorithm;
@@ -172,13 +197,90 @@ class Validator {
172197
.allow_algorithm(key);
173198

174199
verifier.verify(jwt);
200+
201+
bool must_verify_everything = true;
202+
if (jwt.has_payload_claim("ver")) {
203+
const jwt::claim &claim = jwt.get_payload_claim("ver");
204+
if (claim.get_type() != jwt::claim::type::string) {
205+
throw jwt::token_verification_exception("'ver' claim value must be a string (if present)");
206+
}
207+
std::string ver_string = claim.as_string();
208+
if (ver_string == "scitoken:2.0") must_verify_everything = false;
209+
else if (ver_string == "scitokens:1.0") must_verify_everything = m_validate_all_claims;
210+
else {
211+
std::stringstream ss;
212+
ss << "Unknown profile version in token: " << ver_string;
213+
throw jwt::token_verification_exception(ss.str());
214+
}
215+
} else {
216+
must_verify_everything = m_validate_all_claims;
217+
}
218+
219+
for (const auto &claim_pair : jwt.get_payload_claims()) {
220+
if (claim_pair.first == "iat" || claim_pair.first == "nbf" || claim_pair.first == "exp") {
221+
continue;
222+
}
223+
auto iter = m_validators.find(claim_pair.first);
224+
if (iter == m_validators.end() || iter->second.empty()) {
225+
bool is_issuer = claim_pair.first == "iss";
226+
if (is_issuer && !m_allowed_issuers.empty()) {
227+
// skip; we verified it above
228+
} else if (must_verify_everything) {
229+
std::stringstream ss;
230+
ss << "'" << claim_pair.first << "' claim verification is mandatory";
231+
throw jwt::token_verification_exception(ss.str());
232+
}
233+
}
234+
for (const auto verification_func : iter->second) {
235+
const jwt::claim &claim = jwt.get_payload_claim(claim_pair.first);
236+
if (claim.get_type() != jwt::claim::type::string) {
237+
std::stringstream ss;
238+
ss << "'" << claim_pair.first << "' claim value must be a string to verify.";
239+
throw jwt::token_verification_exception(ss.str());
240+
}
241+
std::string value = claim.as_string();
242+
char * err_msg = nullptr;
243+
if (verification_func(value.c_str(), &err_msg)) {
244+
if (err_msg) {
245+
throw jwt::token_verification_exception(err_msg);
246+
} else {
247+
std::stringstream ss;
248+
ss << "'" << claim_pair.first << "' claim verification failed.";
249+
throw jwt::token_verification_exception(ss.str());
250+
}
251+
}
252+
}
253+
}
254+
}
255+
256+
void add_critical_claims(const std::vector<std::string> &claims) {
257+
std::copy(claims.begin(), claims.end(), std::back_inserter(m_critical_claims));
258+
}
259+
260+
void add_allowed_issuers(const std::vector<std::string> &allowed_issuers) {
261+
std::copy(allowed_issuers.begin(), allowed_issuers.end(), std::back_inserter(m_allowed_issuers));
262+
}
263+
264+
void add_string_validator(const std::string &claim, ValidatorFunction func) {
265+
auto result = m_validators.insert({claim, std::vector<ValidatorFunction>()});
266+
result.first->second.push_back(func);
267+
}
268+
269+
void set_validate_all_claims_scitokens_1(bool new_val) {
270+
m_validate_all_claims = new_val;
175271
}
176272

177273
private:
178274
void get_public_key_pem(const std::string &issuer, const std::string &kid, std::string &public_pem, std::string &algorithm);
179275
void get_public_keys_from_web(const std::string &issuer, picojson::value &keys, int64_t &next_update, int64_t &expires);
180276
bool get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update);
181277
bool store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires);
278+
279+
bool m_validate_all_claims{true};
280+
ClaimValidatorMap m_validators;
281+
282+
std::vector<std::string> m_critical_claims;
283+
std::vector<std::string> m_allowed_issuers;
182284
};
183285

184286
}

src/test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ int main(int argc, const char** argv) {
4444
//std::string test_value = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1lczI1NiJ9.eyJpc3MiOiJodHRwczovL2RlbW8uc2NpdG9rZW5zLm9yZyIsImV4cCI6MTU0NjM4OTU5MiwiaWF0IjoxNTQ2Mzg4OTkyLCJuYmYiOjE1NDYzODg5OTIsImp0aSI6IjRkMzM2MTU5LWMxMDEtNGRhYy1iYzI5LWI5NDQ3ZDRkY2IxZSJ9.VfSCPj79IfdVCZHw8n0RJJupbaSU0OqMWxRVAnVUNvk1SCz0Ep3O06Boe5I0SRiZR8_0jzHw9vHZ0YOT_0kPAw";
4545
std::string test_value = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1yczI1NiJ9.eyJpc3MiOiJodHRwczovL2RlbW8uc2NpdG9rZW5zLm9yZyIsImV4cCI6MTU0NjM5MjAwOSwiaWF0IjoxNTQ2MzkxNDA5LCJuYmYiOjE1NDYzOTE0MDksImp0aSI6ImFkYTk2MjdiLWExMGYtNGMyYS05Nzc2LTE4ZThkN2JmN2M4NSJ9.cNMG5zI2-JHh7l_PUPUAxom5Vi6Q3akKmv6q57CoVKHtxZAZRc47Uoix_AH3Xzr42qohr2FPamRTxUMsfZjrAFDJ_4JhJ-kKjJ3cRXXF-gj7lbniCDGOBuPXeMsVmeED15nauZ3XKXUHTGLEsg5O6RjS7sGKM_e9YiYvcTvWXcdkrkxZ2dPPU-R3IxdK6PtE9OB2XOk85H670OAJT3qimKm8Dk_Ri6DEEty1Su_1Tov3ac5B19iZkbhhVPMVP0cRolR9UNLhMxQAsbgEmArQOcs046AOzqQz6osOkdYOrVVO7lO2owUyMol94mB_39y1M8jcf5WNq3ukMMIzMCAPwA";
4646

47-
if (scitoken_deserialize(test_value.c_str(), &scitoken, &err_msg)) {
47+
if (scitoken_deserialize(test_value.c_str(), &scitoken, nullptr, &err_msg)) {
4848
std::cout << "Failed to deserialize a token: " << err_msg << std::endl;
4949
return 1;
5050
}

src/verify.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ int main(int argc, const char** argv) {
1111

1212
SciToken scitoken;
1313
char *err_msg = nullptr;
14-
if (scitoken_deserialize(token.c_str(), &scitoken, &err_msg)) {
14+
if (scitoken_deserialize(token.c_str(), &scitoken, nullptr, &err_msg)) {
1515
std::cout << "Failed to deserialize a token: " << err_msg << std::endl;
1616
return 1;
1717
}

0 commit comments

Comments
 (0)