// ---------------------------------------------------------------------- // File: EosTok.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2019 CERN/ASwitzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ /** * @file EosTok.cc * * @brief Class providing EOS token support * */ #ifdef __APPLE__ #define EBADE 52 #define EKEYEXPIRED 127 #endif #include "EosTok.hh" #include "proto/ConsoleRequest.pb.h" #include #include "common/SymKeys.hh" #include "common/StringConversion.hh" #include "common/Path.hh" #include #include #include #include EOSCOMMONNAMESPACE_BEGIN std::atomic EosTok::sTokenGeneration; EosTok::EosTok() { share = std::make_shared(); valid = false; } EosTok::~EosTok() { } EosTok::EosTok(eos::console::TokenEnclosure& token) { share = std::make_shared(); share->CopyFrom(token); valid = false; } std::string EosTok::Write(const std::string& key) { valid = false; share->set_seed(std::rand()); // create a unique id for this token share->mutable_token()->set_voucher( eos::common::StringConversion::random_uuidstring()); if (Serialize()) { return ""; } std::string rkey = std::to_string(share->seed()) + key + std::to_string( share->seed()); Sign(rkey); std::string os; share->SerializeToString(&os); std::string zb64os; eos::common::SymKey::ZBase64(os, zb64os); zb64os.replace(0, 5, "zteos"); eos::common::StringConversion::Replace(zb64os, '/', '_'); eos::common::StringConversion::Replace(zb64os, '+', '-'); // encode the padding ssize_t pad = 0; if (zb64os.back() == '=') { zb64os.pop_back(); pad++; } if (zb64os.back() == '=') { zb64os.pop_back(); pad++; } for (auto i = 0; i < pad; i++) { zb64os += "%3d"; } return zb64os; } int EosTok::Read(const std::string& zb64is, const std::string& key, uint64_t generation, bool ignoreerror) { std::string is; std::string nzb64is(zb64is); if (nzb64is.substr(0, 5) != "zteos") { return -EINVAL; } if ((nzb64is.substr(0, 10) == "zteos64%3A") || (nzb64is.substr(0, 10) == "zteos64%3a")) { // support URL encoding nzb64is.replace(0, 10, "zbase64:"); } else { nzb64is.replace(0, 5, "zbase"); } eos::common::StringConversion::Replace(nzb64is, '_', '/'); eos::common::StringConversion::Replace(nzb64is, '-', '+'); // deocde the padding size_t l = nzb64is.length(); ssize_t l1 = l - 6; ssize_t l2 = l - 3; ssize_t pad = 0; if (l1 >= 0) { if ((nzb64is.substr(l1, 3) == "%3d") || (nzb64is.substr(l1, 3) == "%3D")) { pad++; } } if (l2 >= 0) { if ((nzb64is.substr(l2, 3) == "%3d") || (nzb64is.substr(l2, 3) == "%3D")) { pad++; } } nzb64is.erase(l - (pad * 3)); for (auto i = 0; i < pad; ++i) { nzb64is += "="; } if (!eos::common::SymKey::ZDeBase64(nzb64is, is)) { return -EINVAL; } if (!share->ParseFromString(is)) { return -EINVAL; } Deserialize(); time_t now = time(NULL); if (!ignoreerror) { if ((time_t)share->token().expires() < now) { return -EKEYEXPIRED; } if (generation != share->token().generation()) { return -EACCES; } } return Verify(key); } int EosTok::Sign(const std::string& key) { std::string nkey(key); std::string nserialized = share->serialized(); share->set_signature(eos::common::SymKey::HmacSha256(nkey, nserialized)); return 0; } int EosTok::Verify(const std::string& key) { std::string nkey = std::to_string(share->seed()) + key + std::to_string( share->seed()); std::string nserialized = share->serialized(); std::string sign = eos::common::SymKey::HmacSha256(nkey, nserialized); if (sign != share->signature()) { return -EPERM; } valid = true; return 0; } int EosTok::Serialize() { std::string os; share->token().SerializeToString(&os); share->set_serialized(os); return 0; } int EosTok::Deserialize() { return !share->mutable_token()->ParseFromString(share->serialized()); } int EosTok::Dump(std::string& dump, bool filtersec, bool oneline) { dump = ""; google::protobuf::util::JsonPrintOptions options; options.add_whitespace = true; options.always_print_primitive_fields = true; (void) google::protobuf::util::MessageToJsonString(*share, &dump, options); if (filtersec) { std::istringstream f(dump); std::string line; std::string filtereddump; while (std::getline(f, line)) { if ((line.find("\"signature\"") != std::string::npos) || (line.find("\"serialized\"") != std::string::npos) || (line.find("\"voucher\"") != std::string::npos) || (line.find("\"requester\"") != std::string::npos) || (line.find("\"seed\"") != std::string::npos)) { } else { filtereddump += line; if (!oneline) { filtereddump += "\n"; } } } dump = filtereddump; } return 0; } int EosTok::Reset() { share->Clear(); valid = false; return 0; } int EosTok::SetPath(const std::string& path, bool subtree) { share->mutable_token()->set_path(path); share->mutable_token()->set_allowtree(subtree); return 0; } int EosTok::SetPermission(const std::string& perm) { share->mutable_token()->set_permission(perm); return 0; } int EosTok::SetOwner(const std::string& owner) { share->mutable_token()->set_owner(owner); return 0; } int EosTok::SetGroup(const std::string& group) { share->mutable_token()->set_group(group); return 0; } int EosTok::SetExpires(time_t expires) { share->mutable_token()->set_expires(expires); return 0; } int EosTok::SetGeneration(uint64_t generation) { share->mutable_token()->set_generation(generation); return 0; } int EosTok::SetRequester(const std::string& requester) { share->mutable_token()->set_requester(requester); return 0; } int EosTok::AddOrigin(const std::string& host, const std::string& name, const std::string& prot) { eos::console::TokenAuth* auth = share->mutable_token()->add_origins(); auth->set_prot(prot); auth->set_host(host); auth->set_name(name); return 0; } int EosTok::VerifyOrigin(const std::string& host, const std::string& name, const std::string& prot) { // if no origin is defined, it always matches if (!share->token().origins_size()) { return 0; } for (int i = 0; i < share->token().origins_size(); ++i) { const eos::console::TokenAuth& auth = share->token().origins(i); int m1 = Match(host, auth.host()); int m2 = Match(name, auth.name()); int m3 = Match(prot, auth.prot()); if ((m1 < 0) || (m2 < 0) || (m3 < 0)) { return -EBADE; } if ((m1 == 1) && (m2 == 1) && (m3 == 1)) { return 0; } } return -ENODATA; } int EosTok::Match(const std::string& input, const std::string& regexString) { try { std::regex re(regexString); bool match = std::regex_match(input, re); return match; } catch (regex_error& e) { std::cerr << "error: invalid regex : " << e.what() << " : " << "CODE IS: " << e.code() << std::endl; return -1; } } int EosTok::ValidatePath(const std::string& path) const { if (share->token().allowtree()) { // this is a tree permission if (path.substr(0, share->token().path().length()) != share->token().path()) { return -EACCES; } } else { if ((path.back() == '/') && (share->token().path().back() != '/')) { eos::common::Path cPath(share->token().path()); if (path == cPath.GetParentPath()) { return 0; } } // this is an exact permission if (path != share->token().path()) { return -EACCES; } } return 0; } bool EosTok::Valid() const { return valid; } std::string EosTok::Owner() const { return share->token().owner(); } std::string EosTok::Group() const { return share->token().group(); } int EosTok::Generation() const { return share->token().generation(); } std::string EosTok::Permission() const { return share->token().permission(); } std::string EosTok::Path() const { return share->token().path(); } std::string EosTok::Voucher() const { return share->token().voucher(); } time_t EosTok::Expires() const { return share->token().expires(); } std::string EosTok::Requester() const { return share->token().requester(); } bool EosTok::IsEosToken(XrdOucEnv* env) { static const std::string http_enc_tag = "Bearer%20zteos64"; static const std::string http_tag = "Bearer zteos64"; static const std::string tag = "zteos64"; if (env) { const char* authz_opaque = env->Get("authz"); if (authz_opaque) { if (strncmp(authz_opaque, http_enc_tag.c_str(), http_enc_tag.length()) == 0) { return true; } if (strncmp(authz_opaque, http_tag.c_str(), http_tag.length()) == 0) { return true; } if (strncmp(authz_opaque, tag.c_str(), tag.length()) == 0) { return true; } } } return false; } EOSCOMMONNAMESPACE_END