From 4949e91e09bc1d16132090aef4dc09cc6ca09fa1 Mon Sep 17 00:00:00 2001 From: amon Date: Fri, 2 Feb 2018 04:34:48 +0800 Subject: [PATCH 1/2] Added mandatory authentication to all RPC interfaces and a corresponding override flag. --- src/JsonRpcServer/JsonRpcServer.cpp | 22 +++++++++++++++-- src/JsonRpcServer/JsonRpcServer.h | 5 +++- .../PaymentServiceJsonRpcServer.cpp | 23 ++++++++++++++++-- src/PaymentGate/PaymentServiceJsonRpcServer.h | 2 +- src/PaymentGateService/PaymentGateService.cpp | 2 +- .../PaymentServiceConfiguration.cpp | 22 +++++++++++++++++ .../PaymentServiceConfiguration.h | 2 ++ src/Rpc/JsonRpc.cpp | 1 + src/Rpc/JsonRpc.h | 11 +++++++++ src/SimpleWallet/SimpleWallet.cpp | 12 ++++++++++ src/Wallet/WalletRpcServer.cpp | 24 +++++++++++++++++++ src/Wallet/WalletRpcServer.h | 4 ++++ 12 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/JsonRpcServer/JsonRpcServer.cpp b/src/JsonRpcServer/JsonRpcServer.cpp index 3f77d2801f..101d763889 100755 --- a/src/JsonRpcServer/JsonRpcServer.cpp +++ b/src/JsonRpcServer/JsonRpcServer.cpp @@ -37,11 +37,12 @@ namespace CryptoNote { -JsonRpcServer::JsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, Logging::ILogger& loggerGroup) : +JsonRpcServer::JsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, Logging::ILogger& loggerGroup, PaymentService::Configuration& config) : HttpServer(sys, loggerGroup), system(sys), stopEvent(stopEvent), - logger(loggerGroup, "JsonRpcServer") + logger(loggerGroup, "JsonRpcServer"), + config(config) { } @@ -164,6 +165,23 @@ void JsonRpcServer::makeMethodNotFoundResponse(Common::JsonValue& resp) { resp.insert("error", error); } +void JsonRpcServer::makeInvalidPasswordResponse(Common::JsonValue& resp) { + using Common::JsonValue; + + JsonValue error(JsonValue::OBJECT); + + JsonValue code; + code = static_cast(-32604); + + JsonValue message; + message = "Invalid or no rpc password"; + + error.insert("code", code); + error.insert("message", message); + + resp.insert("error", error); +} + void JsonRpcServer::fillJsonResponse(const Common::JsonValue& v, Common::JsonValue& resp) { resp.insert("result", v); } diff --git a/src/JsonRpcServer/JsonRpcServer.h b/src/JsonRpcServer/JsonRpcServer.h index ab6cee2660..97f2690d8c 100755 --- a/src/JsonRpcServer/JsonRpcServer.h +++ b/src/JsonRpcServer/JsonRpcServer.h @@ -24,6 +24,7 @@ #include "Logging/ILogger.h" #include "Logging/LoggerRef.h" #include "Rpc/HttpServer.h" +#include "PaymentGateService/PaymentServiceConfiguration.h" namespace CryptoNote { @@ -43,7 +44,7 @@ namespace CryptoNote { class JsonRpcServer : HttpServer { public: - JsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, Logging::ILogger& loggerGroup); + JsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, Logging::ILogger& loggerGroup, PaymentService::Configuration& config); JsonRpcServer(const JsonRpcServer&) = delete; void start(const std::string& bindAddress, uint16_t bindPort); @@ -51,12 +52,14 @@ class JsonRpcServer : HttpServer { protected: static void makeErrorResponse(const std::error_code& ec, Common::JsonValue& resp); static void makeMethodNotFoundResponse(Common::JsonValue& resp); + static void makeInvalidPasswordResponse(Common::JsonValue& resp); static void makeGenericErrorReponse(Common::JsonValue& resp, const char* what, int errorCode = -32001); static void fillJsonResponse(const Common::JsonValue& v, Common::JsonValue& resp); static void prepareJsonResponse(const Common::JsonValue& req, Common::JsonValue& resp); static void makeJsonParsingErrorResponse(Common::JsonValue& resp); virtual void processJsonRpcRequest(const Common::JsonValue& req, Common::JsonValue& resp) = 0; + PaymentService::Configuration& config; private: // HttpServer diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp index 324cbd1aaf..2c106dc291 100755 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.cpp +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.cpp @@ -25,10 +25,12 @@ #include "Serialization/JsonInputValueSerializer.h" #include "Serialization/JsonOutputStreamSerializer.h" +#include "Rpc/JsonRpc.h" + namespace PaymentService { -PaymentServiceJsonRpcServer::PaymentServiceJsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, WalletService& service, Logging::ILogger& loggerGroup) - : JsonRpcServer(sys, stopEvent, loggerGroup) +PaymentServiceJsonRpcServer::PaymentServiceJsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, WalletService& service, Logging::ILogger& loggerGroup, PaymentService::Configuration& config) + : JsonRpcServer(sys, stopEvent, loggerGroup, config) , service(service) , logger(loggerGroup, "PaymentServiceJsonRpcServer") { @@ -60,6 +62,23 @@ PaymentServiceJsonRpcServer::PaymentServiceJsonRpcServer(System::Dispatcher& sys void PaymentServiceJsonRpcServer::processJsonRpcRequest(const Common::JsonValue& req, Common::JsonValue& resp) { try { prepareJsonResponse(req, resp); + + if (!config.legacySecurity) { + std::string clientPassword; + if (!req.contains("password")) { + makeInvalidPasswordResponse(resp); + return; + } + if (!req("password").isString()) { + makeInvalidPasswordResponse(resp); + return; + } + clientPassword = req("password").getString(); + if (clientPassword != config.rpcPassword) { + makeInvalidPasswordResponse(resp); + return; + } + } if (!req.contains("method")) { logger(Logging::WARNING) << "Field \"method\" is not found in json request: " << req; diff --git a/src/PaymentGate/PaymentServiceJsonRpcServer.h b/src/PaymentGate/PaymentServiceJsonRpcServer.h index 49b47dbd12..569ee5d909 100755 --- a/src/PaymentGate/PaymentServiceJsonRpcServer.h +++ b/src/PaymentGate/PaymentServiceJsonRpcServer.h @@ -31,7 +31,7 @@ class WalletService; class PaymentServiceJsonRpcServer : public CryptoNote::JsonRpcServer { public: - PaymentServiceJsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, WalletService& service, Logging::ILogger& loggerGroup); + PaymentServiceJsonRpcServer(System::Dispatcher& sys, System::Event& stopEvent, WalletService& service, Logging::ILogger& loggerGroup, PaymentService::Configuration& config); PaymentServiceJsonRpcServer(const PaymentServiceJsonRpcServer&) = delete; protected: diff --git a/src/PaymentGateService/PaymentGateService.cpp b/src/PaymentGateService/PaymentGateService.cpp index f2b79fd990..5f576de804 100755 --- a/src/PaymentGateService/PaymentGateService.cpp +++ b/src/PaymentGateService/PaymentGateService.cpp @@ -290,7 +290,7 @@ void PaymentGateService::runWalletService(const CryptoNote::Currency& currency, std::cout << "Address: " << address << std::endl; } } else { - PaymentService::PaymentServiceJsonRpcServer rpcServer(*dispatcher, *stopEvent, *service, logger); + PaymentService::PaymentServiceJsonRpcServer rpcServer(*dispatcher, *stopEvent, *service, logger, config.gateConfiguration); rpcServer.start(config.gateConfiguration.bindAddress, config.gateConfiguration.bindPort); Logging::LoggerRef(logger, "PaymentGateService")(Logging::INFO, Logging::BRIGHT_WHITE) << "JSON-RPC server stopped, stopping wallet service..."; diff --git a/src/PaymentGateService/PaymentServiceConfiguration.cpp b/src/PaymentGateService/PaymentServiceConfiguration.cpp index 22edf15216..adc859997c 100755 --- a/src/PaymentGateService/PaymentServiceConfiguration.cpp +++ b/src/PaymentGateService/PaymentServiceConfiguration.cpp @@ -42,12 +42,16 @@ Configuration::Configuration() { bindPort = 0; secretViewKey = ""; secretSpendKey = ""; + rpcPassword = ""; + legacySecurity = false; } void Configuration::initOptions(boost::program_options::options_description& desc) { desc.add_options() ("bind-address", po::value()->default_value("0.0.0.0"), "payment service bind address") ("bind-port", po::value()->default_value(8070), "payment service bind port") + ("rpc-password", po::value(), "Specify the password to access the rpc server.") + ("rpc-legacy-security", "Enable legacy mode (no password for RPC). WARNING: INSECURE. USE ONLY AS A LAST RESORT.") ("container-file,w", po::value(), "container file") ("container-password,p", po::value(), "container password") ("generate-container,g", "generate new container file with one wallet and exit") @@ -152,6 +156,24 @@ void Configuration::init(const boost::program_options::variables_map& options) { throw ConfigurationError("container-file parameter are required"); } } + + // If generating a container skip the authentication parameters. + if (generateNewContainer) { + return; + } + + // Check for the authentication parameters + if ((options.count("rpc-password") == 0) && (options.count("rpc-legacy-security") == 0)) { + throw ConfigurationError("Please specify an RPC password or use the --rpc-legacy-security flag."); + } + + if (options.count("rpc-legacy-security") != 0) { + legacySecurity = true; + } + else { + rpcPassword = options["rpc-password"].as(); + } + } } //namespace PaymentService diff --git a/src/PaymentGateService/PaymentServiceConfiguration.h b/src/PaymentGateService/PaymentServiceConfiguration.h index f628d528d8..44c9e3cc5c 100755 --- a/src/PaymentGateService/PaymentServiceConfiguration.h +++ b/src/PaymentGateService/PaymentServiceConfiguration.h @@ -38,6 +38,7 @@ struct Configuration { std::string bindAddress; uint16_t bindPort; + std::string rpcPassword; std::string containerFile; std::string containerPassword; @@ -53,6 +54,7 @@ struct Configuration { bool testnet; bool printAddresses; bool syncFromZero; + bool legacySecurity; size_t logLevel; }; diff --git a/src/Rpc/JsonRpc.cpp b/src/Rpc/JsonRpc.cpp index c9cd54d560..d855191da8 100755 --- a/src/Rpc/JsonRpc.cpp +++ b/src/Rpc/JsonRpc.cpp @@ -31,6 +31,7 @@ JsonRpcError::JsonRpcError(int c) : code(c) { case errMethodNotFound: message = "Method not found"; break; case errInvalidParams: message = "Invalid params"; break; case errInternalError: message = "Internal error"; break; + case errInvalidPassword: message = "Invalid or no password supplied"; break; default: message = "Unknown error"; break; } } diff --git a/src/Rpc/JsonRpc.h b/src/Rpc/JsonRpc.h index f98fee9afe..0f4da62852 100755 --- a/src/Rpc/JsonRpc.h +++ b/src/Rpc/JsonRpc.h @@ -37,6 +37,7 @@ const int errInvalidRequest = -32600; const int errMethodNotFound = -32601; const int errInvalidParams = -32602; const int errInternalError = -32603; +const int errInvalidPassword = -32604; class JsonRpcError: public std::exception { public: @@ -62,6 +63,7 @@ class JsonRpcError: public std::exception { }; typedef boost::optional OptionalId; +typedef boost::optional OptionalPassword; class JsonRpcRequest { public: @@ -84,6 +86,10 @@ class JsonRpcRequest { if (psReq.contains("id")) { id = psReq("id"); } + + if (psReq.contains("password")) { + password = psReq("password"); + } return true; } @@ -112,6 +118,10 @@ class JsonRpcRequest { const OptionalId& getId() const { return id; } + + const OptionalPassword& getPassword() const { + return password; + } std::string getBody() { psReq.set("jsonrpc", std::string("2.0")); @@ -123,6 +133,7 @@ class JsonRpcRequest { Common::JsonValue psReq; OptionalId id; + OptionalPassword password; std::string method; }; diff --git a/src/SimpleWallet/SimpleWallet.cpp b/src/SimpleWallet/SimpleWallet.cpp index 482cd89698..201c4afd47 100755 --- a/src/SimpleWallet/SimpleWallet.cpp +++ b/src/SimpleWallet/SimpleWallet.cpp @@ -1329,6 +1329,18 @@ int main(int argc, char* argv[]) { if (command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_bind_port)) { //runs wallet with rpc interface + + /* + If the rpc interface is run, ensure that either legacy mode or an RPC + password is set. + */ + + if (!command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_password) && + !command_line::has_arg(vm, Tools::wallet_rpc_server::arg_rpc_legacy_security)) { + logger(ERROR, BRIGHT_RED) << "Required RPC password is not set."; + return 1; + } + if (!command_line::has_arg(vm, arg_wallet_file)) { logger(ERROR, BRIGHT_RED) << "Wallet file not set."; return 1; diff --git a/src/Wallet/WalletRpcServer.cpp b/src/Wallet/WalletRpcServer.cpp index 5895e82525..9d72a5c46d 100755 --- a/src/Wallet/WalletRpcServer.cpp +++ b/src/Wallet/WalletRpcServer.cpp @@ -36,11 +36,16 @@ namespace Tools { const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port = { "rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server", 0, true }; const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip = { "rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1" }; +const command_line::arg_descriptor wallet_rpc_server::arg_rpc_password = { "rpc-password", "Specify the password to access the rpc server.", "", true }; +const command_line::arg_descriptor wallet_rpc_server::arg_rpc_legacy_security = { "rpc-legacy-security", "Enable legacy mode (no password for RPC). WARNING: INSECURE. USE ONLY AS A LAST RESORT.", false}; const command_line::arg_descriptor arg_allow_extended_rpc = {"allow-extended-rpc", "Allow RPC access to the wallet address and view/spend keys", false}; + void wallet_rpc_server::init_options(boost::program_options::options_description& desc) { command_line::add_arg(desc, arg_rpc_bind_ip); command_line::add_arg(desc, arg_rpc_bind_port); + command_line::add_arg(desc, arg_rpc_password); + command_line::add_arg(desc, arg_rpc_legacy_security); command_line::add_arg(desc, arg_allow_extended_rpc); } //------------------------------------------------------------------------------------------------------------------------------ @@ -80,6 +85,10 @@ void wallet_rpc_server::send_stop_signal() { bool wallet_rpc_server::handle_command_line(const boost::program_options::variables_map& vm) { m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); m_port = command_line::get_arg(vm, arg_rpc_bind_port); + m_legacy = command_line::get_arg(vm, arg_rpc_legacy_security); + if (!m_legacy) { + m_password = command_line::get_arg(vm, arg_rpc_password); + } m_allow_extended_rpc = command_line::get_arg(vm, arg_allow_extended_rpc); return true; } @@ -99,10 +108,25 @@ void wallet_rpc_server::processRequest(const CryptoNote::HttpRequest& request, C JsonRpcRequest jsonRequest; JsonRpcResponse jsonResponse; + std::string clientPassword; try { jsonRequest.parseRequest(request.getBody()); jsonResponse.setId(jsonRequest.getId()); + + if (!m_legacy) { + const JsonRpc::OptionalPassword& clientPasswordObject = jsonRequest.getPassword(); + if (!clientPasswordObject.is_initialized()) { + throw JsonRpcError(errInvalidPassword); + } + if (!clientPasswordObject.get().isString()) { + throw JsonRpcError(errInvalidPassword); + } + clientPassword = clientPasswordObject.get().getString(); + if (clientPassword != m_password) { + throw JsonRpcError(errInvalidPassword); + } + } static std::unordered_map s_methods = { { "getbalance", makeMemberMethod(&wallet_rpc_server::on_getbalance) }, diff --git a/src/Wallet/WalletRpcServer.h b/src/Wallet/WalletRpcServer.h index cf172f8e4d..0a73c829cc 100755 --- a/src/Wallet/WalletRpcServer.h +++ b/src/Wallet/WalletRpcServer.h @@ -53,6 +53,8 @@ namespace Tools static const command_line::arg_descriptor arg_rpc_bind_port; static const command_line::arg_descriptor arg_rpc_bind_ip; + static const command_line::arg_descriptor arg_rpc_password; + static const command_line::arg_descriptor arg_rpc_legacy_security; private: @@ -77,7 +79,9 @@ namespace Tools CryptoNote::INode& m_node; uint16_t m_port; bool m_allow_extended_rpc; + bool m_legacy; std::string m_bind_ip; + std::string m_password; CryptoNote::Currency& m_currency; const std::string m_walletFilename; From db53d4636d2ad7f4eda4970cdcd08d53b3025b52 Mon Sep 17 00:00:00 2001 From: amon Date: Fri, 2 Feb 2018 04:59:56 +0800 Subject: [PATCH 2/2] Added 'transfer' to the list of extended RPC methods in simplewallet requiring an explicit flag to enable these methods. Updated existing methods to throw an error informing the user that their request has been restricted when attempting to access extended methods without authorisation. --- src/Wallet/WalletRpcServer.cpp | 16 +++++++++++++++- src/Wallet/WalletRpcServerErrorCodes.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Wallet/WalletRpcServer.cpp b/src/Wallet/WalletRpcServer.cpp index 5895e82525..3aece82733 100755 --- a/src/Wallet/WalletRpcServer.cpp +++ b/src/Wallet/WalletRpcServer.cpp @@ -106,12 +106,12 @@ void wallet_rpc_server::processRequest(const CryptoNote::HttpRequest& request, C static std::unordered_map s_methods = { { "getbalance", makeMemberMethod(&wallet_rpc_server::on_getbalance) }, - { "transfer", makeMemberMethod(&wallet_rpc_server::on_transfer) }, { "store", makeMemberMethod(&wallet_rpc_server::on_store) }, { "get_payments", makeMemberMethod(&wallet_rpc_server::on_get_payments) }, { "get_transfers", makeMemberMethod(&wallet_rpc_server::on_get_transfers) }, { "get_height", makeMemberMethod(&wallet_rpc_server::on_get_height) }, // below are the restricted methods, use --enable-extended-rpc + { "transfer", makeMemberMethod(&wallet_rpc_server::on_transfer) }, { "reset", makeMemberMethod(&wallet_rpc_server::on_reset) }, { "stop_wallet", makeMemberMethod(&wallet_rpc_server::on_stop_wallet) }, { "get_address", makeMemberMethod(&wallet_rpc_server::on_get_address) }, @@ -142,6 +142,11 @@ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE: } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res) { + + if(!m_allow_extended_rpc) { + throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_METHOD_RESTRICTED, "Unable to invoke extended RPC method without explicit --allow-extended-rpc flag."); + } + std::vector transfers; for (auto it = req.destinations.begin(); it != req.destinations.end(); it++) { CryptoNote::WalletLegacyTransfer transfer; @@ -311,6 +316,9 @@ bool wallet_rpc_server::on_stop_wallet(const wallet_rpc::COMMAND_RPC_STOP::reque wallet_rpc_server::send_stop_signal(); } + else { + throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_METHOD_RESTRICTED, "Unable to invoke extended RPC method without explicit --allow-extended-rpc flag."); + } return true; } @@ -320,6 +328,9 @@ bool wallet_rpc_server::on_get_address(const wallet_rpc::COMMAND_RPC_GET_ADDRESS if(m_allow_extended_rpc) { res.address = m_wallet.getAddress(); } + else { + throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_METHOD_RESTRICTED, "Unable to invoke extended RPC method without explicit --allow-extended-rpc flag."); + } return true; } @@ -332,6 +343,9 @@ bool wallet_rpc_server::on_view_keys(const wallet_rpc::COMMAND_RPC_VIEW_KEYS::re res.view_key = Common::podToHex(keys.viewSecretKey); res.spend_key = Common::podToHex(keys.spendSecretKey); } + else { + throw JsonRpc::JsonRpcError(WALLET_RPC_ERROR_METHOD_RESTRICTED, "Unable to invoke extended RPC method without explicit --allow-extended-rpc flag."); + } return true; } diff --git a/src/Wallet/WalletRpcServerErrorCodes.h b/src/Wallet/WalletRpcServerErrorCodes.h index b18ec574b7..0cbb2a1a66 100755 --- a/src/Wallet/WalletRpcServerErrorCodes.h +++ b/src/Wallet/WalletRpcServerErrorCodes.h @@ -23,3 +23,4 @@ #define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3 #define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4 #define WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID -5 +#define WALLET_RPC_ERROR_METHOD_RESTRICTED -6