@@ -69,8 +69,6 @@ class HTTPRPCTimerInterface : public RPCTimerInterface
69
69
};
70
70
71
71
72
- /* Pre-base64-encoded authentication token */
73
- static std::string strRPCUserColonPass;
74
72
/* Stored RPC timer interface (for unregistration) */
75
73
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
76
74
/* List of -rpcauth values */
@@ -101,31 +99,27 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq
101
99
102
100
// This function checks username and password against -rpcauth
103
101
// entries from config file.
104
- static bool multiUserAuthorized (std::string strUserPass )
102
+ static bool CheckUserAuthorized (std::string_view user_pass )
105
103
{
106
- if (strUserPass .find (' :' ) == std::string::npos) {
104
+ if (user_pass .find (' :' ) == std::string::npos) {
107
105
return false ;
108
106
}
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 );
111
109
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)) {
115
112
continue ;
116
113
}
117
114
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 ];
120
117
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);
123
121
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)) {
129
123
return true ;
130
124
}
131
125
}
@@ -145,12 +139,7 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
145
139
if (strUserPass.find (' :' ) != std::string::npos)
146
140
strAuthUsernameOut = strUserPass.substr (0 , strUserPass.find (' :' ));
147
141
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);
154
143
}
155
144
156
145
static bool HTTPReq_JSONRPC (const std::any& context, HTTPRequest* req)
@@ -291,6 +280,8 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
291
280
292
281
static bool InitRPCAuthentication ()
293
282
{
283
+ std::string user_colon_pass;
284
+
294
285
if (gArgs .GetArg (" -rpcpassword" , " " ) == " " )
295
286
{
296
287
std::optional<fs::perms> cookie_perms{std::nullopt};
@@ -304,19 +295,41 @@ static bool InitRPCAuthentication()
304
295
cookie_perms = *perm_opt;
305
296
}
306
297
307
- assert (strRPCUserColonPass.empty ()); // Only support initializing once
308
- if (!GenerateAuthCookie (&strRPCUserColonPass, cookie_perms)) {
298
+ if (!GenerateAuthCookie (&user_colon_pass, cookie_perms)) {
309
299
return false ;
310
300
}
311
- if (strRPCUserColonPass .empty ()) {
301
+ if (user_colon_pass .empty ()) {
312
302
LogInfo (" RPC authentication cookie file generation is disabled." );
313
303
} else {
314
304
LogInfo (" Using random cookie authentication." );
315
305
}
316
306
} else {
317
307
LogInfo (" Using rpcuser/rpcpassword authentication." );
318
308
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});
320
333
}
321
334
322
335
if (!gArgs .GetArgs (" -rpcauth" ).empty ()) {
0 commit comments