Auto merge of #1999 - str4d:1950-random-cookie-rpc-auth, r=ebfull

rpc: Implement random-cookie based authentication

Cherry-picked from bitcoin/bitcoin#6388.

Closes #1950.
This commit is contained in:
zkbot 2017-01-18 14:20:25 +00:00
commit 94f427a211
4 changed files with 106 additions and 29 deletions

View File

@ -96,12 +96,6 @@ static bool AppInitRPC(int argc, char* argv[])
Object CallRPC(const string& strMethod, const Array& params) Object CallRPC(const string& strMethod, const Array& params)
{ {
if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "")
throw runtime_error(strprintf(
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
"If the file does not exist, create it with owner-readable-only file permissions."),
GetConfigFile().string().c_str()));
// Connect to localhost // Connect to localhost
bool fUseSSL = GetBoolArg("-rpcssl", false); bool fUseSSL = GetBoolArg("-rpcssl", false);
boost::asio::io_service io_service; boost::asio::io_service io_service;
@ -115,10 +109,24 @@ Object CallRPC(const string& strMethod, const Array& params)
if (!fConnected) if (!fConnected)
throw CConnectionFailed("couldn't connect to server"); throw CConnectionFailed("couldn't connect to server");
// Find credentials to use
std::string strRPCUserColonPass;
if (mapArgs["-rpcpassword"] == "") {
// Try fall back to cookie-based authentication if no password is provided
if (!GetAuthCookie(&strRPCUserColonPass)) {
throw runtime_error(strprintf(
_("You must set rpcpassword=<password> in the configuration file:\n%s\n"
"If the file does not exist, create it with owner-readable-only file permissions."),
GetConfigFile().string().c_str()));
}
} else {
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
// HTTP basic authentication // HTTP basic authentication
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);
map<string, string> mapRequestHeaders; map<string, string> mapRequestHeaders;
mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass);
// Send request // Send request
string strRequest = JSONRPCRequest(strMethod, params, 1); string strRequest = JSONRPCRequest(strMethod, params, 1);

View File

@ -6,6 +6,7 @@
#include "rpcprotocol.h" #include "rpcprotocol.h"
#include "clientversion.h" #include "clientversion.h"
#include "random.h"
#include "tinyformat.h" #include "tinyformat.h"
#include "util.h" #include "util.h"
#include "utilstrencodings.h" #include "utilstrencodings.h"
@ -13,6 +14,7 @@
#include "version.h" #include "version.h"
#include <stdint.h> #include <stdint.h>
#include <fstream>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
@ -288,3 +290,68 @@ Object JSONRPCError(int code, const string& message)
error.push_back(Pair("message", message)); error.push_back(Pair("message", message));
return error; return error;
} }
/** Username used when cookie authentication is in use (arbitrary, only for
* recognizability in debugging/logging purposes)
*/
static const std::string COOKIEAUTH_USER = "__cookie__";
/** Default name for auth cookie file */
static const std::string COOKIEAUTH_FILE = ".cookie";
boost::filesystem::path GetAuthCookieFile()
{
boost::filesystem::path path(GetArg("-rpccookiefile", COOKIEAUTH_FILE));
if (!path.is_complete()) path = GetDataDir() / path;
return path;
}
bool GenerateAuthCookie(std::string *cookie_out)
{
unsigned char rand_pwd[32];
GetRandBytes(rand_pwd, 32);
std::string cookie = COOKIEAUTH_USER + ":" + EncodeBase64(&rand_pwd[0],32);
/** the umask determines what permissions are used to create this file -
* these are set to 077 in init.cpp unless overridden with -sysperms.
*/
std::ofstream file;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open()) {
LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath.string());
return false;
}
file << cookie;
file.close();
LogPrintf("Generated RPC authentication cookie %s\n", filepath.string());
if (cookie_out)
*cookie_out = cookie;
return true;
}
bool GetAuthCookie(std::string *cookie_out)
{
std::ifstream file;
std::string cookie;
boost::filesystem::path filepath = GetAuthCookieFile();
file.open(filepath.string().c_str());
if (!file.is_open())
return false;
std::getline(file, cookie);
file.close();
if (cookie_out)
*cookie_out = cookie;
return true;
}
void DeleteAuthCookie()
{
try {
boost::filesystem::remove(GetAuthCookieFile());
} catch (const boost::filesystem::filesystem_error& e) {
LogPrintf("%s: Unable to remove random auth cookie file: %s\n", __func__, e.what());
}
}

View File

@ -14,6 +14,7 @@
#include <boost/iostreams/stream.hpp> #include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/asio/ssl.hpp> #include <boost/asio/ssl.hpp>
#include <boost/filesystem.hpp>
#include "json/json_spirit_reader_template.h" #include "json/json_spirit_reader_template.h"
#include "json/json_spirit_utils.h" #include "json/json_spirit_utils.h"
@ -165,4 +166,13 @@ json_spirit::Object JSONRPCReplyObj(const json_spirit::Value& result, const json
std::string JSONRPCReply(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id); std::string JSONRPCReply(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id);
json_spirit::Object JSONRPCError(int code, const std::string& message); json_spirit::Object JSONRPCError(int code, const std::string& message);
/** Get name of RPC authentication cookie file */
boost::filesystem::path GetAuthCookieFile();
/** Generate a new RPC authentication cookie and write it to disk */
bool GenerateAuthCookie(std::string *cookie_out);
/** Read the RPC authentication cookie from disk */
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
#endif // BITCOIN_RPCPROTOCOL_H #endif // BITCOIN_RPCPROTOCOL_H

View File

@ -621,29 +621,19 @@ void StartRPCThreads()
strAllowed += subnet.ToString() + " "; strAllowed += subnet.ToString() + " ";
LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed);
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; if (mapArgs["-rpcpassword"] == "")
if (((mapArgs["-rpcpassword"] == "") ||
(mapArgs["-rpcuser"] == mapArgs["-rpcpassword"])) && Params().RequireRPCPassword())
{ {
unsigned char rand_pwd[32]; LogPrintf("No rpcpassword set - using random cookie authentication\n");
GetRandBytes(rand_pwd, 32); if (!GenerateAuthCookie(&strRPCUserColonPass)) {
uiInterface.ThreadSafeMessageBox(strprintf( uiInterface.ThreadSafeMessageBox(
_("To use zcashd you must set an rpcpassword in the configuration file:\n" _("Error: A fatal internal error occured, see debug.log for details"), // Same message as AbortNode
"%s\n" "", CClientUIInterface::MSG_ERROR);
"It is recommended you use the following random password:\n"
"rpcuser=zcashrpc\n"
"rpcpassword=%s\n"
"(you do not need to remember this password)\n"
"The username and password MUST NOT be the same.\n"
"If the file does not exist, create it with owner-readable-only file permissions.\n"
"It is also recommended to set alertnotify so you are notified of problems;\n"
"for example: alertnotify=echo %%s | mail -s \"Zcash Alert\" admin@foo.com\n"),
GetConfigFile().string(),
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32)),
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::SECURE);
StartShutdown(); StartShutdown();
return; return;
} }
} else {
strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"];
}
assert(rpc_io_service == NULL); assert(rpc_io_service == NULL);
rpc_io_service = new boost::asio::io_service(); rpc_io_service = new boost::asio::io_service();
@ -808,6 +798,8 @@ void StopRPCThreads()
} }
deadlineTimers.clear(); deadlineTimers.clear();
DeleteAuthCookie();
rpc_io_service->stop(); rpc_io_service->stop();
g_rpcSignals.Stopped(); g_rpcSignals.Stopped();
if (rpc_worker_group != NULL) if (rpc_worker_group != NULL)