-rpcssl=1 option, allowing secure HTTPS JSON-RPC connections on OSX/Unix.

git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@165 1a98c847-1fd6-4fd8-948a-caf3550aa51b
This commit is contained in:
gavinandresen 2010-10-11 18:23:41 +00:00
parent 83082f04a4
commit ed54768f5f
5 changed files with 139 additions and 14 deletions

View File

@ -175,8 +175,22 @@ bool AppInit2(int argc, char* argv[])
" -connect=<ip> \t " + _("Connect only to the specified node\n") + " -connect=<ip> \t " + _("Connect only to the specified node\n") +
" -server \t " + _("Accept command line and JSON-RPC commands\n") + " -server \t " + _("Accept command line and JSON-RPC commands\n") +
" -daemon \t " + _("Run in the background as a daemon and accept commands\n") + " -daemon \t " + _("Run in the background as a daemon and accept commands\n") +
" -rpcuser \t " + _("Username for JSON-RPC connection\n") +
" -rpcpassword \t " + _("Password for JSON-RPC connection\n") +
" -rpcport=<port> \t " + _("Listen for JSON-RPC connections on <port>\n") +
" -rpcallowip=<ip>\t " + _("Allow JSON-RPC connections from specified IP address\n") +
" -rpcconnect=<ip>\t " + _("Send commands to node running on <ip>\n") +
" -? \t " + _("This help message\n"); " -? \t " + _("This help message\n");
#ifdef USE_SSL
strUsage += string() +
_("\nSSL options: (see the Bitcoin Wiki for SSL setup instructions)\n") +
" -rpcssl=1 \t " + _("Use OpenSSL (https) for JSON-RPC connections\n") +
" -rpcsslcertificatchainfile=<file.cert>\t " + _("Server certificate file (default: server.cert)\n") +
" -rpcsslprivatekeyfile=<file.pem> \t " + _("Server private key (default: server.pem)\n") +
" -rpcsslciphers=<ciphers> \t " + _("Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH)\n");
#endif
#if defined(__WXMSW__) && defined(GUI) #if defined(__WXMSW__) && defined(GUI)
// Tabs make the columns line up in the message box // Tabs make the columns line up in the message box
wxMessageBox(strUsage, "Bitcoin", wxOK); wxMessageBox(strUsage, "Bitcoin", wxOK);

View File

@ -22,9 +22,10 @@ LIBS= -dead_strip \
$(DEPSDIR)/lib/libboost_filesystem.a \ $(DEPSDIR)/lib/libboost_filesystem.a \
$(DEPSDIR)/lib/libboost_program_options.a \ $(DEPSDIR)/lib/libboost_program_options.a \
$(DEPSDIR)/lib/libboost_thread.a \ $(DEPSDIR)/lib/libboost_thread.a \
$(DEPSDIR)/lib/libssl.a \
$(DEPSDIR)/lib/libcrypto.a $(DEPSDIR)/lib/libcrypto.a
DEFS=$(shell $(DEPSDIR)/bin/wx-config --cxxflags) -D__WXMAC_OSX__ -DNOPCH -DMSG_NOSIGNAL=0 DEFS=$(shell $(DEPSDIR)/bin/wx-config --cxxflags) -D__WXMAC_OSX__ -DNOPCH -DMSG_NOSIGNAL=0 -DUSE_SSL
DEBUGFLAGS=-g -DwxDEBUG_LEVEL=0 DEBUGFLAGS=-g -DwxDEBUG_LEVEL=0
# ppc doesn't work because we don't support big-endian # ppc doesn't work because we don't support big-endian

View File

@ -23,11 +23,12 @@ LIBS= \
-l boost_program_options \ -l boost_program_options \
-l boost_thread \ -l boost_thread \
-l db_cxx \ -l db_cxx \
-l ssl \
-l crypto \ -l crypto \
-Wl,-Bdynamic \ -Wl,-Bdynamic \
-l gthread-2.0 -l gthread-2.0
DEFS=-D__WXGTK__ -DNOPCH -DFOURWAYSSE2 DEFS=-D__WXGTK__ -DNOPCH -DFOURWAYSSE2 -DUSE_SSL
DEBUGFLAGS=-g -D__WXDEBUG__ DEBUGFLAGS=-g -D__WXDEBUG__
CFLAGS=-O2 -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) CFLAGS=-O2 -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS)
HEADERS=headers.h strlcpy.h serialize.h uint256.h util.h key.h bignum.h base58.h \ HEADERS=headers.h strlcpy.h serialize.h uint256.h util.h key.h bignum.h base58.h \

131
rpc.cpp
View File

@ -5,6 +5,12 @@
#include "headers.h" #include "headers.h"
#undef printf #undef printf
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#ifdef USE_SSL
#include <boost/asio/ssl.hpp>
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SSLStream;
#endif
#include "json/json_spirit_reader_template.h" #include "json/json_spirit_reader_template.h"
#include "json/json_spirit_writer_template.h" #include "json/json_spirit_writer_template.h"
#include "json/json_spirit_utils.h" #include "json/json_spirit_utils.h"
@ -14,7 +20,7 @@
// a certain size around 145MB. If we need access to json_spirit outside this // a certain size around 145MB. If we need access to json_spirit outside this
// file, we could use the compiled json_spirit option. // file, we could use the compiled json_spirit option.
using boost::asio::ip::tcp; using namespace boost::asio;
using namespace json_spirit; using namespace json_spirit;
void ThreadRPCServer2(void* parg); void ThreadRPCServer2(void* parg);
@ -777,7 +783,7 @@ string HTTPReply(int nStatus, const string& strMsg)
strMsg.c_str()); strMsg.c_str());
} }
int ReadHTTPStatus(tcp::iostream& stream) int ReadHTTPStatus(std::basic_istream<char>& stream)
{ {
string str; string str;
getline(stream, str); getline(stream, str);
@ -788,7 +794,7 @@ int ReadHTTPStatus(tcp::iostream& stream)
return atoi(vWords[1].c_str()); return atoi(vWords[1].c_str());
} }
int ReadHTTPHeader(tcp::iostream& stream, map<string, string>& mapHeadersRet) int ReadHTTPHeader(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
{ {
int nLen = 0; int nLen = 0;
loop loop
@ -812,7 +818,7 @@ int ReadHTTPHeader(tcp::iostream& stream, map<string, string>& mapHeadersRet)
return nLen; return nLen;
} }
int ReadHTTP(tcp::iostream& stream, map<string, string>& mapHeadersRet, string& strMessageRet) int ReadHTTP(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet, string& strMessageRet)
{ {
mapHeadersRet.clear(); mapHeadersRet.clear();
strMessageRet = ""; strMessageRet = "";
@ -930,8 +936,59 @@ bool ClientAllowed(const string& strAddress)
return false; return false;
} }
#ifdef USE_SSL
//
// IOStream device that speaks SSL but can also speak non-SSL
//
class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> {
public:
SSLIOStreamDevice(SSLStream &streamIn, bool fUseSSLIn) : stream(streamIn)
{
fUseSSL = fUseSSLIn;
fNeedHandshake = fUseSSLIn;
}
void handshake(ssl::stream_base::handshake_type role)
{
if (!fNeedHandshake) return;
fNeedHandshake = false;
stream.handshake(role);
}
std::streamsize read(char* s, std::streamsize n)
{
handshake(ssl::stream_base::server); // HTTPS servers read first
if (fUseSSL) return stream.read_some(asio::buffer(s, n));
return stream.next_layer().read_some(asio::buffer(s, n));
}
std::streamsize write(const char* s, std::streamsize n)
{
handshake(ssl::stream_base::client); // HTTPS clients write first
if (fUseSSL) return asio::write(stream, asio::buffer(s, n));
return asio::write(stream.next_layer(), asio::buffer(s, n));
}
bool connect(const std::string& server, const std::string& port)
{
ip::tcp::resolver resolver(stream.get_io_service());
ip::tcp::resolver::query query(server.c_str(), port.c_str());
ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
ip::tcp::resolver::iterator end;
boost::system::error_code error = asio::error::host_not_found;
while (error && endpoint_iterator != end)
{
stream.lowest_layer().close();
stream.lowest_layer().connect(*endpoint_iterator++, error);
}
if (error)
return false;
return true;
}
private:
bool fNeedHandshake;
bool fUseSSL;
SSLStream& stream;
};
#endif
void ThreadRPCServer(void* parg) void ThreadRPCServer(void* parg)
{ {
@ -972,18 +1029,54 @@ void ThreadRPCServer2(void* parg)
return; return;
} }
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally bool fUseSSL = (mapArgs.count("-rpcssl") > 0);
boost::asio::io_service io_service; asio::ip::address bindAddress = mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback();
tcp::endpoint endpoint(mapArgs.count("-rpcallowip") ? asio::ip::address_v4::any() : asio::ip::address_v4::loopback(), 8332);
tcp::acceptor acceptor(io_service, endpoint); asio::io_service io_service;
ip::tcp::endpoint endpoint(bindAddress, GetArg("-rpcport", 8332));
ip::tcp::acceptor acceptor(io_service, endpoint);
#ifdef USE_SSL
ssl::context context(io_service, ssl::context::sslv23);
if (fUseSSL)
{
context.set_options(ssl::context::no_sslv2);
filesystem::path certfile = GetArg("-rpcsslcertificatechainfile", "server.cert");
if (!certfile.is_complete()) certfile = filesystem::path(GetDataDir()) / certfile;
if (filesystem::exists(certfile)) context.use_certificate_chain_file(certfile.string().c_str());
else printf("ThreadRPCServer ERROR: missing server certificate file %s\n", certfile.string().c_str());
filesystem::path pkfile = GetArg("-rpcsslprivatekeyfile", "server.pem");
if (!pkfile.is_complete()) pkfile = filesystem::path(GetDataDir()) / pkfile;
if (filesystem::exists(pkfile)) context.use_private_key_file(pkfile.string().c_str(), ssl::context::pem);
else printf("ThreadRPCServer ERROR: missing server private key file %s\n", pkfile.string().c_str());
string ciphers = GetArg("-rpcsslciphers",
"TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH");
SSL_CTX_set_cipher_list(context.impl(), ciphers.c_str());
}
#else
if (fUseSSL)
throw runtime_error("-rpcssl=true, but bitcoin compiled without full openssl libraries.");
#endif
loop loop
{ {
// Accept connection // Accept connection
tcp::iostream stream; #ifdef USE_SSL
tcp::endpoint peer; SSLStream sslStream(io_service, context);
SSLIOStreamDevice d(sslStream, fUseSSL);
iostreams::stream<SSLIOStreamDevice> stream(d);
#else
ip::tcp::iostream stream;
#endif
ip::tcp::endpoint peer;
vnThreadsRunning[4]--; vnThreadsRunning[4]--;
#ifdef USE_SSL
acceptor.accept(sslStream.lowest_layer(), peer);
#else
acceptor.accept(*stream.rdbuf(), peer); acceptor.accept(*stream.rdbuf(), peer);
#endif
vnThreadsRunning[4]++; vnThreadsRunning[4]++;
if (fShutdown) if (fShutdown)
return; return;
@ -1102,9 +1195,25 @@ Object CallRPC(const string& strMethod, const Array& params)
GetConfigFile().c_str())); GetConfigFile().c_str()));
// Connect to localhost // Connect to localhost
tcp::iostream stream(GetArg("-rpcconnect", "127.0.0.1"), "8332"); bool fUseSSL = (mapArgs.count("-rpcssl") > 0);
#ifdef USE_SSL
asio::io_service io_service;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::no_sslv2);
SSLStream sslStream(io_service, context);
SSLIOStreamDevice d(sslStream, fUseSSL);
iostreams::stream<SSLIOStreamDevice> stream(d);
if (!d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332")))
throw runtime_error("couldn't connect to server");
#else
if (fUseSSL)
throw runtime_error("-rpcssl=true, but bitcoin compiled without full openssl libraries.");
ip::tcp::iostream stream(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", "8332"));
if (stream.fail()) if (stream.fail())
throw runtime_error("couldn't connect to server"); throw runtime_error("couldn't connect to server");
#endif
// HTTP basic authentication // HTTP basic authentication
string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]);

View File

@ -22,7 +22,7 @@ class CDataStream;
class CAutoFile; class CAutoFile;
static const unsigned int MAX_SIZE = 0x02000000; static const unsigned int MAX_SIZE = 0x02000000;
static const int VERSION = 31303; static const int VERSION = 31304;
static const char* pszSubVer = ""; static const char* pszSubVer = "";