Skip to content

Commit fd5dfda

Browse files
committed
Merge pull request #6388
0937290 doc: mention RPC random cookie authentication in release notes (Wladimir J. van der Laan) 71cbeaa rpc: Implement random-cookie based authentication (Wladimir J. van der Laan)
2 parents bb59e78 + 0937290 commit fd5dfda

File tree

5 files changed

+120
-27
lines changed

5 files changed

+120
-27
lines changed

doc/release-notes.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ release-notes at release time)
44
Notable changes
55
===============
66

7+
Random-cookie RPC authentication
8+
---------------------------------
9+
10+
When no `-rpcpassword` is specified, the daemon now uses a special 'cookie'
11+
file for authentication. This file is generated with random content when the
12+
daemon starts, and deleted when it exits. Its contents are used as
13+
authentication token. Read access to this file controls who can access through
14+
RPC. By default it is stored in the data directory but its location can be
15+
overridden with the option `-rpccookiefile`.
16+
17+
This is similar to Tor's CookieAuthentication: see
18+
https://www.torproject.org/docs/tor-manual.html.en
19+
20+
This allows running bitcoind without having to do any manual configuration.
21+
722
Example header
823
----------------------
924

src/bitcoin-cli.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ static bool AppInitRPC(int argc, char* argv[])
9797

9898
UniValue CallRPC(const string& strMethod, const UniValue& params)
9999
{
100-
if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "")
101-
throw runtime_error(strprintf(
102-
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
103-
"If the file does not exist, create it with owner-readable-only file permissions."),
104-
GetConfigFile().string().c_str()));
105-
106100
// Connect to localhost
107101
bool fUseSSL = GetBoolArg("-rpcssl", false);
108102
boost::asio::io_service io_service;
@@ -116,10 +110,24 @@ UniValue CallRPC(const string& strMethod, const UniValue& params)
116110
if (!fConnected)
117111
throw CConnectionFailed("couldn't connect to server");
118112

113+
// Find credentials to use
114+
std::string strRPCUserColonPass;
115+
if (mapArgs["-rpcpassword"] == "") {
116+
// Try fall back to cookie-based authentication if no password is provided
117+
if (!GetAuthCookie(&strRPCUserColonPass)) {
118+
throw runtime_error(strprintf(
119+
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
120+
"If the file does not exist, create it with owner-readable-only file permissions."),
121+
GetConfigFile().string().c_str()));
122+
123+
}
124+
} else {
125+
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
126+
}
127+
119128
// HTTP basic authentication
120-
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
121129
map<string, string> mapRequestHeaders;
122-
mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64;
130+
mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
123131

124132
// Send request
125133
string strRequest = JSONRPCRequest(strMethod, params, 1);

src/rpcprotocol.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
#include "rpcprotocol.h"
77

88
#include "clientversion.h"
9+
#include "random.h"
910
#include "tinyformat.h"
1011
#include "util.h"
1112
#include "utilstrencodings.h"
1213
#include "utiltime.h"
1314
#include "version.h"
1415

1516
#include <stdint.h>
17+
#include <fstream>
1618

1719
#include <boost/algorithm/string.hpp>
1820
#include <boost/asio.hpp>
@@ -287,3 +289,68 @@ UniValue JSONRPCError(int code, const string& message)
287289
error.push_back(Pair("message", message));
288290
return error;
289291
}
292+
293+
/** Username used when cookie authentication is in use (arbitrary, only for
294+
* recognizability in debugging/logging purposes)
295+
*/
296+
static const std::string COOKIEAUTH_USER = "__cookie__";
297+
/** Default name for auth cookie file */
298+
static const std::string COOKIEAUTH_FILE = ".cookie";
299+
300+
boost::filesystem::path GetAuthCookieFile()
301+
{
302+
boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
303+
if (!path.is_complete()) path = GetDataDir() / path;
304+
return path;
305+
}
306+
307+
bool GenerateAuthCookie(std::string *cookie_out)
308+
{
309+
unsigned char rand_pwd[32];
310+
GetRandBytes(rand_pwd, 32);
311+
std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
312+
313+
/** the umask determines what permissions are used to create this file -
314+
* these are set to 077 in init.cpp unless overridden with -sysperms.
315+
*/
316+
std::ofstream file;
317+
boost::filesystem::path filepath = GetAuthCookieFile();
318+
file.open(filepath.string().c_str());
319+
if (!file.is_open()) {
320+
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
321+
return false;
322+
}
323+
file << cookie;
324+
file.close();
325+
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
326+
327+
if (cookie_out)
328+
*cookie_out = cookie;
329+
return true;
330+
}
331+
332+
bool GetAuthCookie(std::string *cookie_out)
333+
{
334+
std::ifstream file;
335+
std::string cookie;
336+
boost::filesystem::path filepath = GetAuthCookieFile();
337+
file.open(filepath.string().c_str());
338+
if (!file.is_open())
339+
return false;
340+
std::getline(file, cookie);
341+
file.close();
342+
343+
if (cookie_out)
344+
*cookie_out = cookie;
345+
return true;
346+
}
347+
348+
void DeleteAuthCookie()
349+
{
350+
try {
351+
boost::filesystem::remove(GetAuthCookieFile());
352+
} catch (const boost::filesystem::filesystem_error& e) {
353+
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
354+
}
355+
}
356+

src/rpcprotocol.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <boost/iostreams/stream.hpp>
1515
#include <boost/asio.hpp>
1616
#include <boost/asio/ssl.hpp>
17+
#include <boost/filesystem.hpp>
1718

1819
#include "univalue/univalue.h"
1920

@@ -165,4 +166,13 @@ UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const Un
165166
std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id);
166167
UniValue JSONRPCError(int code, const std::string& message);
167168

169+
/** Get name of RPC authentication cookie file */
170+
boost::filesystem::path GetAuthCookieFile();
171+
/** Generate a new RPC authentication cookie and write it to disk */
172+
bool GenerateAuthCookie(std::string *cookie_out);
173+
/** Read the RPC authentication cookie from disk */
174+
bool GetAuthCookie(std::string *cookie_out);
175+
/** Delete RPC authentication cookie from disk */
176+
void DeleteAuthCookie();
177+
168178
#endif // BITCOIN_RPCPROTOCOL_H

src/rpcserver.cpp

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -597,27 +597,18 @@ void StartRPCThreads()
597597
strAllowed += subnet.ToString() + " ";
598598
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
599599

600-
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
601600
if (mapArgs["-rpcpassword"] == "")
602601
{
603-
unsigned char rand_pwd[32];
604-
GetRandBytes(rand_pwd, 32);
605-
uiInterface.ThreadSafeMessageBox(strprintf(
606-
_("To use bitcoind, or the -server option to bitcoin-qt, you must set an rpcpassword in the configuration file:\n"
607-
"%s\n"
608-
"It is recommended you use the following random password:\n"
609-
"rpcuser=bitcoinrpc\n"
610-
"rpcpassword=%s\n"
611-
"(you do not need to remember this password)\n"
612-
"The username and password MUST NOT be the same.\n"
613-
"If the file does not exist, create it with owner-readable-only file permissions.\n"
614-
"It is also recommended to set alertnotify so you are notified of problems;\n"
615-
"for example: alertnotify=echo %%s | mail -s \"Bitcoin Alert\" [email protected]\n"),
616-
GetConfigFile().string(),
617-
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)),
618-
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::SECURE);
619-
StartShutdown();
620-
return;
602+
LogPrintf("No rpcpassword set - using random cookie authentication\n");
603+
if (!GenerateAuthCookie(&strRPCUserColonPass)) {
604+
uiInterface.ThreadSafeMessageBox(
605+
_("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
606+
"", CClientUIInterface::MSG_ERROR);
607+
StartShutdown();
608+
return;
609+
}
610+
} else {
611+
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
621612
}
622613

623614
assert(rpc_io_service == NULL);
@@ -768,6 +759,8 @@ void StopRPCThreads()
768759
}
769760
deadlineTimers.clear();
770761

762+
DeleteAuthCookie();
763+
771764
rpc_io_service->stop();
772765
g_rpcSignals.Stopped();
773766
if (rpc_worker_group != NULL)

0 commit comments

Comments
 (0)