From e49a7274a2141dcb9e188bc4b45c2d7b928ccecd Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Fri, 9 May 2025 16:44:34 +0200 Subject: [PATCH] rpc: Avoid join-split roundtrip for user:pass for auth credentials --- src/httprpc.cpp | 26 +++++++++++--------------- src/rpc/request.cpp | 22 ++++++++++++---------- src/rpc/request.h | 21 +++++++++++++++++++-- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 8c7cd746255..afdb6784aef 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -278,7 +278,8 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) static bool InitRPCAuthentication() { - std::string user_colon_pass; + std::string user; + std::string pass; if (gArgs.GetArg("-rpcpassword", "") == "") { @@ -293,30 +294,25 @@ static bool InitRPCAuthentication() cookie_perms = *perm_opt; } - if (!GenerateAuthCookie(&user_colon_pass, cookie_perms)) { + switch (GenerateAuthCookie(cookie_perms, user, pass)) { + case GenerateAuthCookieResult::ERR: return false; - } - if (user_colon_pass.empty()) { + case GenerateAuthCookieResult::DISABLED: LogInfo("RPC authentication cookie file generation is disabled."); - } else { + break; + case GenerateAuthCookieResult::OK: LogInfo("Using random cookie authentication."); + break; } } else { LogInfo("Using rpcuser/rpcpassword authentication."); LogWarning("The use of rpcuser/rpcpassword is less secure, because credentials are configured in plain text. It is recommended that locally-run instances switch to cookie-based auth, or otherwise to use hashed rpcauth credentials. See share/rpcauth in the source directory for more information."); - user_colon_pass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); + user = gArgs.GetArg("-rpcuser", ""); + pass = gArgs.GetArg("-rpcpassword", ""); } // If there is a plaintext credential, hash it with a random salt before storage. - if (!user_colon_pass.empty()) { - std::vector fields{SplitString(user_colon_pass, ':')}; - if (fields.size() != 2) { - LogError("Unable to parse RPC credentials. The configured rpcuser or rpcpassword cannot contain a \":\"."); - return false; - } - const std::string& user = fields[0]; - const std::string& pass = fields[1]; - + if (!user.empty() || !pass.empty()) { // Generate a random 16 byte hex salt. std::array raw_salt; GetStrongRandBytes(raw_salt); diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 30f4a4f51be..79cab2d8d25 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -97,12 +97,14 @@ static fs::path GetAuthCookieFile(bool temp=false) static bool g_generated_cookie = false; -bool GenerateAuthCookie(std::string* cookie_out, std::optional cookie_perms) +GenerateAuthCookieResult GenerateAuthCookie(const std::optional& cookie_perms, + std::string& user, + std::string& pass) { const size_t COOKIE_SIZE = 32; unsigned char rand_pwd[COOKIE_SIZE]; GetRandBytes(rand_pwd); - std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd); + const std::string rand_pwd_hex{HexStr(rand_pwd)}; /** the umask determines what permissions are used to create this file - * these are set to 0077 in common/system.cpp. @@ -110,27 +112,27 @@ bool GenerateAuthCookie(std::string* cookie_out, std::optional cookie std::ofstream file; fs::path filepath_tmp = GetAuthCookieFile(true); if (filepath_tmp.empty()) { - return true; // -norpccookiefile + return GenerateAuthCookieResult::DISABLED; // -norpccookiefile } file.open(filepath_tmp); if (!file.is_open()) { LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp)); - return false; + return GenerateAuthCookieResult::ERR; } - file << cookie; + file << COOKIEAUTH_USER << ":" << rand_pwd_hex; file.close(); fs::path filepath = GetAuthCookieFile(false); if (!RenameOver(filepath_tmp, filepath)) { LogWarning("Unable to rename cookie authentication file %s to %s", fs::PathToString(filepath_tmp), fs::PathToString(filepath)); - return false; + return GenerateAuthCookieResult::ERR; } if (cookie_perms) { std::error_code code; fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code); if (code) { LogWarning("Unable to set permissions on cookie authentication file %s", fs::PathToString(filepath)); - return false; + return GenerateAuthCookieResult::ERR; } } @@ -138,9 +140,9 @@ bool GenerateAuthCookie(std::string* cookie_out, std::optional cookie LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath)); LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions())); - if (cookie_out) - *cookie_out = cookie; - return true; + user = COOKIEAUTH_USER; + pass = rand_pwd_hex; + return GenerateAuthCookieResult::OK; } bool GetAuthCookie(std::string *cookie_out) diff --git a/src/rpc/request.h b/src/rpc/request.h index 24887e8691c..2fb234c1973 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -23,8 +23,25 @@ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional id, JSONRPCVersion jsonrpc_version); UniValue JSONRPCError(int code, const std::string& message); -/** Generate a new RPC authentication cookie and write it to disk */ -bool GenerateAuthCookie(std::string* cookie_out, std::optional cookie_perms=std::nullopt); +enum class GenerateAuthCookieResult : uint8_t { + DISABLED, // -norpccookiefile + ERR, + OK, +}; + +/** + * Generate a new RPC authentication cookie and write it to disk + * @param[in] cookie_perms Filesystem permissions to use for the cookie file. + * @param[out] user Generated username, only set if `OK` is returned. + * @param[out] pass Generated password, only set if `OK` is returned. + * @retval GenerateAuthCookieResult::DISABLED Authentication via cookie is disabled. + * @retval GenerateAuthCookieResult::ERROR Error occurred, auth data could not be saved to disk. + * @retval GenerateAuthCookieResult::OK Auth data was generated, saved to disk and in `user` and `pass`. + */ +GenerateAuthCookieResult GenerateAuthCookie(const std::optional& cookie_perms, + std::string& user, + std::string& pass); + /** Read the RPC authentication cookie from disk */ bool GetAuthCookie(std::string *cookie_out); /** Delete RPC authentication cookie from disk */