diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fed367..9a7a977 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ find_package( OpenSSL REQUIRED ) find_package( Sqlite3 REQUIRED ) set(LIBCRYPTO_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) set(LIBCRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) +set(CMAKE_MACOSX_RPATH ON) elseif( UNIX ) @@ -60,6 +61,9 @@ target_link_libraries(scitokens-test-access SciTokens) add_executable(scitokens-list-access src/list_access.cpp) target_link_libraries(scitokens-list-access SciTokens) +add_executable(scitokens-create src/create.cpp) +target_link_libraries(scitokens-create SciTokens) + if (NOT DEFINED LIB_INSTALL_DIR) SET(LIB_INSTALL_DIR "lib") endif() diff --git a/src/create.cpp b/src/create.cpp new file mode 100644 index 0000000..7d89774 --- /dev/null +++ b/src/create.cpp @@ -0,0 +1,198 @@ + +#include "scitokens.h" + +#include +#include + +#include +#include +#include +#include + +namespace { + +const char usage[] = \ +"\n" +"Syntax: %s [--cred cred_file] [--key key_file] [--keyid kid]\n" +" [--claim key=val] ...\n" +"\n" +" Options\n" +" -h | --help Display usage\n" +" -c | --cred File containing signing credential.\n" +" -k | --key File containing the signing private key.\n" +" -K | --keyid Name of the token key.\n" +" -i | --issuer Issuer for the token.\n" +" -p | --profile Token profile (wlcg, scitokens1, scitokens2).\n" +"\n"; + +const struct option long_options[] = +{ + {"help", no_argument, NULL, 'h'}, + {"cred", required_argument, NULL, 'c'}, + {"key", required_argument, NULL, 'k'}, + {"keyid", required_argument, NULL, 'K'}, + {"issuer", required_argument, NULL, 'i'}, + {"claim", required_argument, NULL, 'C'}, + {"profile", required_argument, NULL, 'p'}, + {0, 0, 0, 0} +}; + +const char short_options[] = "hc:k:K:i:C:p:"; + +std::string g_cred, g_key, g_kid, g_issuer, g_profile; +std::vector g_claims; + +int init_arguments(int argc, char *argv[]) { + + int arg; + while((arg = getopt_long(argc, argv, short_options, long_options, nullptr)) != -1) + { + switch (arg) + { + case 'h': + printf(usage, argv[0]); + exit(0); + break; + case 'c': + g_cred = optarg; + break; + case 'k': + g_key = optarg; + break; + case 'K': + g_kid = optarg; + break; + case 'i': + g_issuer = optarg; + break; + case 'C': + g_claims.emplace_back(optarg); + break; + case 'p': + g_profile = optarg; + break; + default: + fprintf(stderr, usage, argv[0]); + exit(1); + break; + } + } + + if (optind != argc) { + fprintf(stderr, "%s: invalid option -- %s\n", argv[0], argv[optind]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if (g_cred.empty()) { + fprintf(stderr, "%s: missing --cred option\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if (g_key.empty()) { + fprintf(stderr, "%s: missing --key option\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if (g_kid.empty()) { + fprintf(stderr, "%s: missing --keyid option\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if (g_issuer.empty()) { + fprintf(stderr, "%s: missing --issuer option\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + return 0; +} + +} + +int main(int argc, char *argv[]) { + + int rv = init_arguments(argc, argv); + if (rv) { + return rv; + } + + std::ifstream priv_ifs(g_key); + std::string private_contents( (std::istreambuf_iterator(priv_ifs)), + (std::istreambuf_iterator()) + ); + std::ifstream pub_ifs(g_cred); + std::string public_contents( (std::istreambuf_iterator(pub_ifs)), + (std::istreambuf_iterator()) + ); + + char *err_msg; + auto key_raw = scitoken_key_create(g_kid.c_str(), "ES256", public_contents.c_str(), + private_contents.c_str(), &err_msg); + std::unique_ptr + key(key_raw, scitoken_key_destroy); + if (key_raw == nullptr) { + fprintf(stderr, "Failed to generate a key: %s\n", err_msg); + free(err_msg); + return 1; + } + + std::unique_ptr + token(scitoken_create(key_raw), scitoken_destroy); + if (token.get() == nullptr) { + fprintf(stderr, "Failed to generate a new token.\n"); + return 1; + } + + rv = scitoken_set_claim_string(token.get(), "iss", g_issuer.c_str(), &err_msg); + if (rv) { + fprintf(stderr, "Failed to set issuer: %s\n", err_msg); + free(err_msg); + return 1; + } + + for (const auto &claim : g_claims) { + auto pos = claim.find("="); + if (pos == std::string::npos) { + fprintf(stderr, "Claim must contain a '=' character: %s\n", claim.c_str()); + return 1; + } + auto key = claim.substr(0, pos); + auto val = claim.substr(pos + 1); + + rv = scitoken_set_claim_string(token.get(), key.c_str(), val.c_str(), &err_msg); + if (rv) { + fprintf(stderr, "Failed to set claim (%s=%s): %s\n", key.c_str(), val.c_str(), err_msg); + free(err_msg); + return 1; + } + } + + if (!g_profile.empty()) { + SciTokenProfile profile; + if (g_profile == "wlcg") { + profile = SciTokenProfile::WLCG_1_0; + } else if (g_profile == "scitokens1") { + profile = SciTokenProfile::SCITOKENS_1_0; + } else if (g_profile == "scitokens2") { + profile = SciTokenProfile::SCITOKENS_2_0; + } else { + fprintf(stderr, "Unknown token profile: %s\n", g_profile.c_str()); + return 1; + } + scitoken_set_serialize_mode(token.get(), profile); + } + + char *value; + rv = scitoken_serialize(token.get(), &value, &err_msg); + if (rv) { + fprintf(stderr, "Failed to serialize the token: %s\n", err_msg); + free(err_msg); + return 1; + } + + printf("%s\n", value); +} diff --git a/src/scitokens.cpp b/src/scitokens.cpp index 67881ef..360a028 100644 --- a/src/scitokens.cpp +++ b/src/scitokens.cpp @@ -67,6 +67,14 @@ int scitoken_set_claim_string(SciToken token, const char *key, const char *value } +void scitoken_set_serialize_mode(SciToken token, SciTokenProfile profile) { + scitokens::SciToken *real_token = reinterpret_cast(token); + if (real_token == nullptr) {return;} + + real_token->set_serialize_mode(static_cast(profile)); +} + + int scitoken_get_claim_string(const SciToken token, const char *key, char **value, char **err_msg) { scitokens::SciToken *real_token = reinterpret_cast(token); std::string claim_str; @@ -161,10 +169,39 @@ int scitoken_deserialize(const char *value, SciToken *token, char const* const* return 0; } +int scitoken_store_public_ec_key(const char *issuer, const char *keyid, const char *key, char **err_msg) +{ + bool success; + try { + success = scitokens::Validator::store_public_ec_key(issuer, keyid, key); + } catch (std::exception &exc) { + if (err_msg) { + *err_msg = strdup(exc.what()); + } + return -1; + } + + return success ? 0 : -1; +} + Validator validator_create() { return new Validator(); } + +void validator_destroy(Validator validator) { + scitokens::Validator *real_validator = + reinterpret_cast(validator); + delete real_validator; +} + + +void validator_set_token_profile(Validator validator, SciTokenProfile profile) { + if (validator == nullptr) {return;} + auto real_validator = reinterpret_cast(validator); + real_validator->set_validate_profile(static_cast(profile)); +} + int validator_add(Validator validator, const char *claim, StringValidatorFunction validator_func, char **err_msg) { if (validator == nullptr) { if (err_msg) {*err_msg = strdup("Validator may not be a null pointer");} @@ -257,6 +294,14 @@ void enforcer_acl_free(Acl *acls) { } +void enforcer_set_validate_profile(Enforcer enf, SciTokenProfile profile) { + if (enf == nullptr) {return;} + + auto real_enf = reinterpret_cast(enf); + real_enf->set_validate_profile(static_cast(profile)); +} + + int enforcer_generate_acls(const Enforcer enf, const SciToken scitoken, Acl **acls, char **err_msg) { if (enf == nullptr) { if (err_msg) {*err_msg = strdup("Enforcer may not be a null pointer");} diff --git a/src/scitokens.h b/src/scitokens.h index 7b76acb..a5a704d 100644 --- a/src/scitokens.h +++ b/src/scitokens.h @@ -20,6 +20,21 @@ typedef struct Acl_s { } Acl; +/** + * Determine the mode we will use to validate tokens. + * - COMPAT mode (default) indicates any supported token format + * is acceptable. Where possible, the scope names are translated into + * equivalent SciTokens 1.0 claim names (i.e., storage.read -> read; storage.write -> write). + * - SCITOKENS_1_0, SCITOKENS_2_0, WLCG_1_0: only accept these specific profiles. + * No automatic translation is performed. + */ +typedef enum _profile { + COMPAT = 0, + SCITOKENS_1_0, + SCITOKENS_2_0, + WLCG_1_0 +} SciTokenProfile; + SciTokenKey scitoken_key_create(const char *key_id, const char *algorithm, const char *public_contents, const char *private_contents, char **err_msg); void scitoken_key_destroy(SciTokenKey private_key); @@ -38,20 +53,45 @@ void scitoken_set_lifetime(SciToken token, int lifetime); int scitoken_serialize(const SciToken token, char **value, char **err_msg); +/** + * Set the profile used for serialization; if COMPAT mode is used, then + * the library default is utilized (currently, scitokens 1.0). + */ +void scitoken_set_serialize_mode(SciToken token, SciTokenProfile profile); + int scitoken_deserialize(const char *value, SciToken *token, char const* const* allowed_issuers, char **err_msg); +int scitoken_store_public_ec_key(const char *issuer, const char *keyid, const char *value, char **err_msg); + Validator validator_create(); +/** + * Set the profile used for validating the tokens; COMPAT (default) will accept any known token + * type while others will only support that specific profile. + */ +void validator_set_token_profile(Validator, SciTokenProfile profile); + int validator_add(Validator validator, const char *claim, StringValidatorFunction validator_func, char **err_msg); int validator_add_critical_claims(Validator validator, const char **claims, char **err_msg); int validator_validate(Validator validator, SciToken scitoken, char **err_msg); +/** + * Destroy a validator object. + */ +void validator_destroy(Validator); + Enforcer enforcer_create(const char *issuer, const char **audience, char **err_msg); void enforcer_destroy(Enforcer); +/** + * Set the profile used for enforcing ACLs; when set to COMPAT (default), then the authorizations + * will be converted to SciTokens 1.0-style authorizations (so, WLCG's storage.read becomes read). + */ +void enforcer_set_validate_profile(Enforcer, SciTokenProfile profile); + int enforcer_generate_acls(const Enforcer enf, const SciToken scitokens, Acl **acls, char **err_msg); void enforcer_acl_free(Acl *acls); diff --git a/src/scitokens_internal.cpp b/src/scitokens_internal.cpp index 345165b..9c96b8e 100644 --- a/src/scitokens_internal.cpp +++ b/src/scitokens_internal.cpp @@ -207,6 +207,15 @@ std::string b64url_decode_nopadding(const std::string &input) } +// Base64-encode without padding. +std::string b64url_encode_nopadding(const std::string &input) +{ + std::string result = jwt::base::encode(input); + auto pos = result.find("="); + return result.substr(0, pos); +} + + std::string es256_from_coords(const std::string &x_str, const std::string &y_str) { auto x_decode = b64url_decode_nopadding(x_str); @@ -336,6 +345,9 @@ SciToken::deserialize(const std::string &data, const std::vector al // Set all the claims m_claims = m_decoded->get_payload_claims(); + + // Copy over the profile + m_profile = val.get_profile(); } @@ -481,6 +493,63 @@ Validator::get_public_key_pem(const std::string &issuer, const std::string &kid, } +bool +scitokens::Validator::store_public_ec_key(const std::string &issuer, const std::string &keyid, + const std::string &public_key) +{ + std::unique_ptr pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all); + if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size()) { + return false; + } + std::unique_ptr pkey + (PEM_read_bio_EC_PUBKEY(pubkey_bio.get(), nullptr, nullptr, nullptr), EC_KEY_free); + if (!pkey) {return false;} + + EC_GROUP *params = (EC_GROUP *)EC_KEY_get0_group(pkey.get()); + if (!params) { + throw UnsupportedKeyException("Unable to get OpenSSL EC group"); + } + + const EC_POINT *point = EC_KEY_get0_public_key(pkey.get()); + if (!point) { + throw UnsupportedKeyException("Unable to get OpenSSL EC point"); + } + + std::unique_ptr x_bignum(BN_new(), BN_free); + std::unique_ptr y_bignum(BN_new(), BN_free); + if (!EC_POINT_get_affine_coordinates_GFp(params, point, x_bignum.get(), y_bignum.get(), nullptr)) { + throw UnsupportedKeyException("Unable to get OpenSSL affine coordinates"); + } + + auto x_num = BN_num_bytes(x_bignum.get()); + auto y_num = BN_num_bytes(y_bignum.get()); + std::vector x_bin; x_bin.reserve(x_num); + std::vector y_bin; y_bin.reserve(y_num); + BN_bn2bin(x_bignum.get(), &x_bin[0]); + BN_bn2bin(y_bignum.get(), &y_bin[0]); + std::string x_str(reinterpret_cast(&x_bin[0]), x_num); + std::string y_str(reinterpret_cast(&y_bin[0]), y_num); + + picojson::object key_obj; + key_obj["alg"] = picojson::value("ES256"); + key_obj["kid"] = picojson::value(keyid); + key_obj["use"] = picojson::value("sig"); + key_obj["kty"] = picojson::value("EC"); + key_obj["x"] = picojson::value(b64url_encode_nopadding(x_str)); + key_obj["y"] = picojson::value(b64url_encode_nopadding(y_str)); + std::vector key_list; + key_list.emplace_back(key_obj); + + picojson::object top_obj; + top_obj["keys"] = picojson::value(key_list); + + picojson::value top_value(top_obj); + + auto now = std::time(NULL); + return store_public_keys(issuer, top_value, now + 600, now + 4*3600); +} + + bool scitokens::Enforcer::scope_validator(const jwt::claim &claim, void *myself) { auto me = reinterpret_cast(myself); @@ -491,6 +560,7 @@ scitokens::Enforcer::scope_validator(const jwt::claim &claim, void *myself) { std::string requested_path = normalize_absolute_path(me->m_test_path); auto scope_iter = scope.begin(); //std::cout << "Comparing scope " << scope << " against test accesses " << me->m_test_authz << ":" << requested_path << std::endl; + bool compat_modify = false, compat_create = false, compat_cancel = false; while (scope_iter != scope.end()) { while (*scope_iter == ' ') {scope_iter++;} auto next_scope_iter = std::find(scope_iter, scope.end(), ' '); @@ -507,6 +577,25 @@ scitokens::Enforcer::scope_validator(const jwt::claim &claim, void *myself) { } path = normalize_absolute_path(path); + // If we are in compatibility mode and this is a WLCG token, then translate the authorization + // names to utilize the SciToken-style names. + if (me->m_validate_profile == SciToken::Profile::COMPAT && + me->m_validator.get_profile() == SciToken::Profile::WLCG_1_0) { + if (authz == "storage.read") { + authz = "read"; + } else if (authz == "storage.write") { + authz = "write"; + } else if (authz == "compute.read") { + authz = "condor:/READ"; + } else if (authz == "compute.modify") { + compat_modify = true; + } else if (authz == "compute.create") { + compat_create = true; + } else if (authz == "compute.cancel") { + compat_cancel = true; + } + } + if (me->m_test_authz.empty()) { me->m_gen_acls.emplace_back(authz, path); } else if ((me->m_test_authz == authz) && @@ -516,5 +605,17 @@ scitokens::Enforcer::scope_validator(const jwt::claim &claim, void *myself) { scope_iter = next_scope_iter; } + + // Compatibility mode: the combination on compute modify, create, and cancel mode are equivalent + // to the condor:/WRITE authorization. + if (compat_modify && compat_create && compat_cancel) { + if (me->m_test_authz.empty()) { + me->m_gen_acls.emplace_back("condor", "/WRITE"); + } else if ((me->m_test_authz == "condor") && + (requested_path.substr(0, 6) == "/WRITE")) { + return true; + } + } + return me->m_test_authz.empty(); } diff --git a/src/scitokens_internal.h b/src/scitokens_internal.h index a94200d..ffe34cb 100644 --- a/src/scitokens_internal.h +++ b/src/scitokens_internal.h @@ -27,7 +27,7 @@ class CurlException : public std::runtime_error { class MissingIssuerException : public std::runtime_error { public: MissingIssuerException() - : std::runtime_error("Issuer not specific in claims") + : std::runtime_error("Issuer not specified in claims") {} }; @@ -111,6 +111,14 @@ class SciToken { friend class scitokens::Validator; public: + + enum class Profile { + COMPAT = 0, + SCITOKENS_1_0, + SCITOKENS_2_0, + WLCG_1_0 + }; + SciToken(SciTokenKey &signing_algorithm) : m_key(signing_algorithm) {} @@ -121,6 +129,11 @@ friend class scitokens::Validator; if (key == "iss") {m_issuer_set = true;} } + void + set_serialize_mode(Profile profile) { + m_serialize_profile = profile; + } + const jwt::claim get_claim(const std::string &key) { return m_claims[key]; @@ -162,6 +175,20 @@ friend class scitokens::Validator; uuid_unparse_lower(uuid, uuid_str); m_claims["jti"] = std::string(uuid_str); + if (m_serialize_profile == Profile::SCITOKENS_2_0) { + m_claims["ver"] = std::string("scitokens:2.0"); + auto iter = m_claims.find("aud"); + if (iter == m_claims.end()) { + m_claims["aud"] = std::string("ANY"); + } + } else if (m_serialize_profile == Profile::WLCG_1_0) { + m_claims["wlcg_ver"] = std::string("1.0"); + auto iter = m_claims.find("aud"); + if (iter == m_claims.end()) { + m_claims["aud"] = std::string("https://wlcg.cern.ch/jwt/v1/any"); + } + } + // Set all the payload claims for (auto it : m_claims) { builder.set_payload_claim(it.first, it.second); @@ -176,6 +203,8 @@ friend class scitokens::Validator; private: bool m_issuer_set{false}; int m_lifetime{600}; + Profile m_profile{Profile::SCITOKENS_1_0}; + Profile m_serialize_profile{Profile::COMPAT}; std::unordered_map m_claims; std::unique_ptr m_decoded; SciTokenKey &m_key; @@ -252,19 +281,62 @@ class Validator { throw jwt::token_verification_exception("'ver' claim value must be a string (if present)"); } std::string ver_string = claim.as_string(); - if (ver_string == "scitoken:2.0") { + if (ver_string == "scitokens:2.0") { must_verify_everything = false; + if ((m_validate_profile != SciToken::Profile::COMPAT) && + (m_validate_profile != SciToken::Profile::SCITOKENS_2_0)) + { + throw jwt::token_verification_exception("Invalidate token type; not expecting a SciToken 2.0."); + } + m_profile = SciToken::Profile::SCITOKENS_2_0; if (!jwt.has_payload_claim("aud")) { throw jwt::token_verification_exception("'aud' claim required for SciTokens 2.0 profile"); } } - else if (ver_string == "scitokens:1.0") must_verify_everything = m_validate_all_claims; - else { + else if (ver_string == "scitokens:1.0") { + must_verify_everything = m_validate_all_claims; + if ((m_validate_profile != SciToken::Profile::COMPAT) && + (m_validate_profile != SciToken::Profile::SCITOKENS_1_0)) + { + throw jwt::token_verification_exception("Invalidate token type; not expecting a SciToken 1.0."); + } + m_profile = SciToken::Profile::SCITOKENS_1_0; + } else { std::stringstream ss; ss << "Unknown profile version in token: " << ver_string; throw jwt::token_verification_exception(ss.str()); } + // Handle WLCG common JWT profile. + } else if (jwt.has_payload_claim("wlcg.ver")) { + if ((m_validate_profile != SciToken::Profile::COMPAT) && + (m_validate_profile != SciToken::Profile::WLCG_1_0)) + { + throw jwt::token_verification_exception("Invalidate token type; not expecting a WLCG 1.0."); + } + + m_profile = SciToken::Profile::WLCG_1_0; + must_verify_everything = false; + const jwt::claim &claim = jwt.get_payload_claim("wlcg.ver"); + if (claim.get_type() != jwt::claim::type::string) { + throw jwt::token_verification_exception("'ver' claim value must be a string (if present)"); + } + std::string ver_string = claim.as_string(); + if (ver_string != "1.0") { + std::stringstream ss; + ss << "Unknown WLCG profile version in token: " << ver_string; + throw jwt::token_verification_exception(ss.str()); + } + if (!jwt.has_payload_claim("aud")) { + throw jwt::token_verification_exception("Malformed token: 'aud' claim required for WLCG profile"); + } } else { + if ((m_validate_profile != SciToken::Profile::COMPAT) && + (m_validate_profile != SciToken::Profile::SCITOKENS_1_0)) + { + throw jwt::token_verification_exception("Invalidate token type; not expecting a SciToken 1.0."); + } + + m_profile = SciToken::Profile::SCITOKENS_1_0; must_verify_everything = m_validate_all_claims; } @@ -339,13 +411,43 @@ class Validator { m_validate_all_claims = new_val; } + /** + * Get the profile of the last validated token. + * + * If there has been no validation - or the validation failed, + * then the return value is unspecified. + * + * Will not return Profile::COMPAT. + */ + SciToken::Profile get_profile() const { + if (m_profile == SciToken::Profile::COMPAT) { + throw jwt::token_verification_exception("Token profile has not been set."); + } + return m_profile; + } + + /** + * Set the profile that will be used for validation; COMPAT indicates any supported profile + * is allowable. + */ + void set_validate_profile(SciToken::Profile profile) { + m_validate_profile = profile; + } + + /** + * Store the contents of a public EC key for a given issuer. + */ + static bool store_public_ec_key(const std::string &issuer, const std::string &kid, const std::string &key); + private: void get_public_key_pem(const std::string &issuer, const std::string &kid, std::string &public_pem, std::string &algorithm); void get_public_keys_from_web(const std::string &issuer, picojson::value &keys, int64_t &next_update, int64_t &expires); bool get_public_keys_from_db(const std::string issuer, int64_t now, picojson::value &keys, int64_t &next_update); - bool store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires); + static bool store_public_keys(const std::string &issuer, const picojson::value &keys, int64_t next_update, int64_t expires); bool m_validate_all_claims{true}; + SciToken::Profile m_profile{SciToken::Profile::COMPAT}; + SciToken::Profile m_validate_profile{SciToken::Profile::COMPAT}; ClaimStringValidatorMap m_validators; ClaimValidatorMap m_claim_validators; @@ -377,6 +479,10 @@ class Enforcer { m_validator.add_critical_claims(critical_claims); } + void set_validate_profile(SciToken::Profile profile) { + m_validate_profile = profile; + } + bool test(const SciToken &scitoken, const std::string &authz, const std::string &path) { reset_state(); m_test_path = path; @@ -407,21 +513,26 @@ class Enforcer { static bool aud_validator(const jwt::claim &claim, void *myself) { auto me = reinterpret_cast(myself); + std::vector jwt_audiences; if (claim.get_type() == jwt::claim::type::string) { const std::string &audience = claim.as_string(); - if ((audience == "ANY") && !me->m_audiences.empty()) {return true;} - for (const auto &aud : me->m_audiences) { - if (aud == audience) {return true;} - } + jwt_audiences.push_back(audience); } else if (claim.get_type() == jwt::claim::type::array) { const picojson::array &audiences = claim.as_array(); for (const auto &aud_value : audiences) { - if (!aud_value.is()) {continue;} - std::string audience = aud_value.get(); - if ((audience == "ANY") && !me->m_audiences.empty()) {return true;} - for (const auto &aud : me->m_audiences) { - if (aud == audience) {return true;} - } + const std::string &audience = aud_value.get(); + jwt_audiences.push_back(audience); + } + } + for (const auto &aud_value : jwt_audiences) { + if (((me->m_validator.get_profile() == SciToken::Profile::SCITOKENS_2_0) && (aud_value == "ANY")) || + ((me->m_validator.get_profile() == SciToken::Profile::WLCG_1_0) && (aud_value == "https://wlcg.cern.ch/jwt/v1/any")) + ) + { + return true; + } + for (const auto &aud : me->m_audiences) { + if (aud == aud_value) {return true;} } } return false; @@ -431,8 +542,11 @@ class Enforcer { m_test_path = ""; m_test_authz = ""; m_gen_acls.clear(); + m_validator.set_validate_profile(m_validate_profile); } + SciToken::Profile m_validate_profile{SciToken::Profile::COMPAT}; + std::string m_test_path; std::string m_test_authz; AclsList m_gen_acls; diff --git a/src/verify.cpp b/src/verify.cpp index fc6b548..6141b24 100644 --- a/src/verify.cpp +++ b/src/verify.cpp @@ -1,13 +1,117 @@ -#include #include "scitokens.h" -int main(int argc, const char** argv) { +#include +#include +#include + +namespace { + +const char usage[] = \ +"\n" +"Syntax: %s [--cred cred_file] TOKEN\n" +"\n" +" Options\n" +" -h | --help Display usage\n" +" -c | --cred File containing the signing credential.\n" +" -i | --issuer Issuer of the token to verify.\n" +" -K | --keyid Name of the token key.\n" +" -p | --profile Profile to enforce (wlcg, scitokens1, scitokens2).\n" +"\n"; + +const struct option long_options[] = +{ + {"help", no_argument, NULL, 'h'}, + {"cred", required_argument, NULL, 'c'}, + {"issuer", required_argument, NULL, 'i'}, + {"keyid", required_argument, NULL, 'K'}, + {"profile", required_argument, NULL, 'p'}, + {0, 0, 0, 0} +}; + +const char short_options[] = "hc:i:K:p:"; + +std::string g_cred, g_issuer, g_keyid, g_profile; + +int init_arguments(int argc, char * const argv[]) +{ + int arg; + while((arg = getopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { + switch (arg) + { + case 'h': + printf(usage, argv[0]); + exit(0); + break; + case 'c': + g_cred = optarg; + break; + case 'i': + g_issuer = optarg; + break; + case 'K': + g_keyid = optarg; + break; + case 'p': + g_profile = optarg; + break; + default: + fprintf(stderr, usage, argv[0]); + exit(1); + break; + } + } + + if (optind < argc - 1) { + fprintf(stderr, "%s: invalid option -- %s\n", argv[0], argv[optind]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if (optind == argc) { + fprintf(stderr, "%s: Must provide a token as a requirement\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + if ((!g_cred.empty() || !g_issuer.empty() || !g_keyid.empty()) && + (g_cred.empty() || g_issuer.empty() || g_keyid.empty())) + { + fprintf(stderr, "%s: If --cred, --keyid, or --issuer are set, then all must be set.\n", argv[0]); + fprintf(stderr, usage, argv[0]); + exit(1); + } + + return 0; +} + +} + +int main(int argc, char* const* argv) { if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " (TOKEN)" << std::endl; + fprintf(stderr, "%s: Insufficient arguments; must at least provide a token.\n", argv[0]); + fprintf(stderr, usage, argv[0]); return 1; } - std::string token(argv[1]); + if (init_arguments(argc, argv)) {return 1;} + + std::string token(argv[argc-1]); + + if (!g_issuer.empty()) { + char *err_msg; + + std::ifstream pub_ifs(g_cred); + std::string public_contents( (std::istreambuf_iterator(pub_ifs)), + (std::istreambuf_iterator()) + ); + + auto rv = scitoken_store_public_ec_key(g_issuer.c_str(), g_keyid.c_str(), public_contents.c_str(), &err_msg); + if (rv) { + fprintf(stderr, "%s: %s\n", argv[0], err_msg); + free(err_msg); + return 1; + } + } SciToken scitoken; char *err_msg = nullptr; @@ -16,6 +120,6 @@ int main(int argc, const char** argv) { return 1; } std::cout << "Token deserialization successful." << std::endl; + return 0; } -