@@ -69,8 +69,6 @@ class HTTPRPCTimerInterface : public RPCTimerInterface
6969};
7070
7171
72- /* Pre-base64-encoded authentication token */
73- static std::string strRPCUserColonPass;
7472/* Stored RPC timer interface (for unregistration) */
7573static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
7674/* List of -rpcauth values */
@@ -101,31 +99,27 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq
10199
102100// This function checks username and password against -rpcauth
103101// entries from config file.
104- static bool multiUserAuthorized (std::string strUserPass )
102+ static bool CheckUserAuthorized (std::string_view user_pass )
105103{
106- if (strUserPass .find (' :' ) == std::string::npos) {
104+ if (user_pass .find (' :' ) == std::string::npos) {
107105 return false ;
108106 }
109- std::string strUser = strUserPass .substr (0 , strUserPass .find (' :' ));
110- std::string strPass = strUserPass .substr (strUserPass .find (' :' ) + 1 );
107+ std::string_view user = user_pass .substr (0 , user_pass .find (' :' ));
108+ std::string_view pass = user_pass .substr (user_pass .find (' :' ) + 1 );
111109
112- for (const auto & vFields : g_rpcauth) {
113- std::string strName = vFields[0 ];
114- if (!TimingResistantEqual (strName, strUser)) {
110+ for (const auto & fields : g_rpcauth) {
111+ if (!TimingResistantEqual (std::string_view (fields[0 ]), user)) {
115112 continue ;
116113 }
117114
118- std::string strSalt = vFields [1 ];
119- std::string strHash = vFields [2 ];
115+ const std::string& salt = fields [1 ];
116+ const std::string& hash = fields [2 ];
120117
121- static const unsigned int KEY_SIZE = 32 ;
122- unsigned char out[KEY_SIZE];
118+ std::array<unsigned char , CHMAC_SHA256::OUTPUT_SIZE> out;
119+ CHMAC_SHA256 (UCharCast (salt.data ()), salt.size ()).Write (UCharCast (pass.data ()), pass.size ()).Finalize (out.data ());
120+ std::string hash_from_pass = HexStr (out);
123121
124- CHMAC_SHA256 (reinterpret_cast <const unsigned char *>(strSalt.data ()), strSalt.size ()).Write (reinterpret_cast <const unsigned char *>(strPass.data ()), strPass.size ()).Finalize (out);
125- std::vector<unsigned char > hexvec (out, out+KEY_SIZE);
126- std::string strHashFromPass = HexStr (hexvec);
127-
128- if (TimingResistantEqual (strHashFromPass, strHash)) {
122+ if (TimingResistantEqual (hash_from_pass, hash)) {
129123 return true ;
130124 }
131125 }
@@ -145,12 +139,7 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
145139 if (strUserPass.find (' :' ) != std::string::npos)
146140 strAuthUsernameOut = strUserPass.substr (0 , strUserPass.find (' :' ));
147141
148- // Check if authorized under single-user field.
149- // (strRPCUserColonPass is empty when -norpccookiefile is specified).
150- if (!strRPCUserColonPass.empty () && TimingResistantEqual (strUserPass, strRPCUserColonPass)) {
151- return true ;
152- }
153- return multiUserAuthorized (strUserPass);
142+ return CheckUserAuthorized (strUserPass);
154143}
155144
156145static bool HTTPReq_JSONRPC (const std::any& context, HTTPRequest* req)
@@ -291,6 +280,8 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
291280
292281static bool InitRPCAuthentication ()
293282{
283+ std::string user_colon_pass;
284+
294285 if (gArgs .GetArg (" -rpcpassword" , " " ) == " " )
295286 {
296287 std::optional<fs::perms> cookie_perms{std::nullopt };
@@ -304,19 +295,41 @@ static bool InitRPCAuthentication()
304295 cookie_perms = *perm_opt;
305296 }
306297
307- assert (strRPCUserColonPass.empty ()); // Only support initializing once
308- if (!GenerateAuthCookie (&strRPCUserColonPass, cookie_perms)) {
298+ if (!GenerateAuthCookie (&user_colon_pass, cookie_perms)) {
309299 return false ;
310300 }
311- if (strRPCUserColonPass .empty ()) {
301+ if (user_colon_pass .empty ()) {
312302 LogInfo (" RPC authentication cookie file generation is disabled." );
313303 } else {
314304 LogInfo (" Using random cookie authentication." );
315305 }
316306 } else {
317307 LogInfo (" Using rpcuser/rpcpassword authentication." );
318308 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." );
319- strRPCUserColonPass = gArgs .GetArg (" -rpcuser" , " " ) + " :" + gArgs .GetArg (" -rpcpassword" , " " );
309+ user_colon_pass = gArgs .GetArg (" -rpcuser" , " " ) + " :" + gArgs .GetArg (" -rpcpassword" , " " );
310+ }
311+
312+ // If there is a plaintext credential, hash it with a random salt before storage.
313+ if (!user_colon_pass.empty ()) {
314+ std::vector<std::string> fields{SplitString (user_colon_pass, ' :' )};
315+ if (fields.size () != 2 ) {
316+ LogError (" Unable to parse RPC credentials. The configured rpcuser or rpcpassword cannot contain a \" :\" ." );
317+ return false ;
318+ }
319+ const std::string& user = fields[0 ];
320+ const std::string& pass = fields[1 ];
321+
322+ // Generate a random 16 byte hex salt.
323+ std::array<unsigned char , 16 > raw_salt;
324+ GetStrongRandBytes (raw_salt);
325+ std::string salt = HexStr (raw_salt);
326+
327+ // Compute HMAC.
328+ std::array<unsigned char , CHMAC_SHA256::OUTPUT_SIZE> out;
329+ CHMAC_SHA256 (UCharCast (salt.data ()), salt.size ()).Write (UCharCast (pass.data ()), pass.size ()).Finalize (out.data ());
330+ std::string hash = HexStr (out);
331+
332+ g_rpcauth.push_back ({user, salt, hash});
320333 }
321334
322335 if (!gArgs .GetArgs (" -rpcauth" ).empty ()) {
0 commit comments