diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index ed3848826b..4e5c6fb9b6 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +84,33 @@ std::string createHTTPPost ( return s.str (); } +static +boost::optional +to_uint64(std::string const& s) +{ + if (s.empty()) + return boost::none; + + for (auto c : s) + { + if (!isdigit(c)) + return boost::none; + } + + try + { + std::size_t pos{}; + auto const drops = std::stoul(s, &pos); + if (s.size() != pos) + return boost::none; + return drops; + } + catch (std::exception const&) + { + return boost::none; + } +} + class RPCParser { private: @@ -680,16 +709,9 @@ private: } jvRequest[jss::channel_id] = jvParams[1u].asString (); - try - { - auto const drops = std::stoul (jvParams[2u].asString ()); - (void)drops; // just used for error checking - jvRequest[jss::amount] = jvParams[2u]; - } - catch (std::exception const&) - { - return rpcError (rpcCHANNEL_AMT_MALFORMED); - } + if (!jvParams[2u].isString() || !to_uint64(jvParams[2u].asString())) + return rpcError(rpcCHANNEL_AMT_MALFORMED); + jvRequest[jss::amount] = jvParams[2u]; return jvRequest; } @@ -699,7 +721,21 @@ private: { std::string const strPk = jvParams[0u].asString (); - if (!parseBase58 (TokenType::TOKEN_ACCOUNT_PUBLIC, strPk)) + bool const validPublicKey = [&strPk]{ + if (parseBase58 (TokenType::TOKEN_ACCOUNT_PUBLIC, strPk)) + return true; + + std::pair pkHex(strUnHex (strPk)); + if (!pkHex.second) + return false; + + if (!publicKeyType(makeSlice(pkHex.first))) + return false; + + return true; + }(); + + if (!validPublicKey) return rpcError (rpcPUBLIC_MALFORMED); Json::Value jvRequest (Json::objectValue); @@ -712,16 +748,11 @@ private: return rpcError (rpcCHANNEL_MALFORMED); } jvRequest[jss::channel_id] = jvParams[1u].asString (); - try - { - auto const drops = std::stoul (jvParams[2u].asString ()); - (void)drops; // just used for error checking - jvRequest[jss::amount] = jvParams[2u]; - } - catch (std::exception const&) - { - return rpcError (rpcCHANNEL_AMT_MALFORMED); - } + + if (!jvParams[2u].isString() || !to_uint64(jvParams[2u].asString())) + return rpcError(rpcCHANNEL_AMT_MALFORMED); + jvRequest[jss::amount] = jvParams[2u]; + jvRequest[jss::signature] = jvParams[3u].asString (); return jvRequest; diff --git a/src/ripple/rpc/handlers/PayChanClaim.cpp b/src/ripple/rpc/handlers/PayChanClaim.cpp index 01a80af0a0..18d7bf7b04 100644 --- a/src/ripple/rpc/handlers/PayChanClaim.cpp +++ b/src/ripple/rpc/handlers/PayChanClaim.cpp @@ -31,8 +31,37 @@ #include #include +#include + namespace ripple { +static +boost::optional +to_uint64(std::string const& s) +{ + if (s.empty()) + return boost::none; + + for (auto c : s) + { + if (!isdigit(c)) + return boost::none; + } + + try + { + std::size_t pos{}; + auto const drops = std::stoul(s, &pos); + if (s.size() != pos) + return boost::none; + return drops; + } + catch (std::exception const&) + { + return boost::none; + } +} + // { // secret_key: // channel_id: 256-bit channel id @@ -54,15 +83,15 @@ Json::Value doChannelAuthorize (RPC::Context& context) if (!channelId.SetHexExact (params[jss::channel_id].asString ())) return rpcError (rpcCHANNEL_MALFORMED); - std::uint64_t drops = 0; - try - { - drops = std::stoul (params[jss::amount].asString ()); - } - catch (std::exception const&) - { + boost::optional const optDrops = + params[jss::amount].isString() + ? to_uint64(params[jss::amount].asString()) + : boost::none; + + if (!optDrops) return rpcError (rpcCHANNEL_AMT_MALFORMED); - } + + std::uint64_t const drops = *optDrops; Serializer msg; serializePayChanAuthorization (msg, channelId, XRPAmount (drops)); @@ -90,29 +119,40 @@ Json::Value doChannelVerify (RPC::Context& context) { auto const& params (context.params); for (auto const& p : - {jss::public_key, jss::channel_id, jss::amount, jss::signature}) + {jss::public_key, jss::channel_id, jss::amount, jss::signature}) if (!params.isMember (p)) return RPC::missing_field_error (p); - std::string const strPk = params[jss::public_key].asString (); - auto const pk = - parseBase58 (TokenType::TOKEN_ACCOUNT_PUBLIC, strPk); - if (!pk) - return rpcError (rpcPUBLIC_MALFORMED); + boost::optional pk; + { + std::string const strPk = params[jss::public_key].asString(); + pk = parseBase58(TokenType::TOKEN_ACCOUNT_PUBLIC, strPk); + + if (!pk) + { + std::pair pkHex(strUnHex (strPk)); + if (!pkHex.second) + return rpcError(rpcPUBLIC_MALFORMED); + auto const pkType = publicKeyType(makeSlice(pkHex.first)); + if (!pkType) + return rpcError(rpcPUBLIC_MALFORMED); + pk.emplace(makeSlice(pkHex.first)); + } + } uint256 channelId; if (!channelId.SetHexExact (params[jss::channel_id].asString ())) return rpcError (rpcCHANNEL_MALFORMED); - std::uint64_t drops = 0; - try - { - drops = std::stoul (params[jss::amount].asString ()); - } - catch (std::exception const&) - { + boost::optional const optDrops = + params[jss::amount].isString() + ? to_uint64(params[jss::amount].asString()) + : boost::none; + + if (!optDrops) return rpcError (rpcCHANNEL_AMT_MALFORMED); - } + + std::uint64_t const drops = *optDrops; std::pair sig(strUnHex (params[jss::signature].asString ())); if (!sig.second || !sig.first.size ()) diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index dcfe4f4ce4..86dd0e1ca9 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -766,15 +766,99 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == c || r[jss::result][jss::channels][1u][jss::channel_id] == c); } + + auto sliceToHex = [](Slice const& slice) { + std::string s; + s.reserve(2 * slice.size()); + for (int i = 0; i < slice.size(); ++i) + { + s += "0123456789ABCDEF"[((slice[i] & 0xf0) >> 4)]; + s += "0123456789ABCDEF"[((slice[i] & 0x0f) >> 0)]; + } + return s; + }; + { // Verify chan1 auth auto const rs = env.rpc ("channel_authorize", "alice", chan1Str, "1000"); auto const sig = rs[jss::result][jss::signature].asString (); BEAST_EXPECT (!sig.empty ()); - auto const rv = env.rpc ( - "channel_verify", chan1PkStr, chan1Str, "1000", sig); - BEAST_EXPECT (rv[jss::result][jss::signature_verified].asBool ()); + { + auto const rv = env.rpc( + "channel_verify", chan1PkStr, chan1Str, "1000", sig); + BEAST_EXPECT(rv[jss::result][jss::signature_verified].asBool()); + } + + { + // use pk hex to verify + auto const pkAsHex = sliceToHex(pk.slice()); + auto const rv = env.rpc ( + "channel_verify", pkAsHex, chan1Str, "1000", sig); + BEAST_EXPECT (rv[jss::result][jss::signature_verified].asBool ()); + } + { + // malformed amount + auto const pkAsHex = sliceToHex(pk.slice()); + auto rv = + env.rpc("channel_verify", pkAsHex, chan1Str, "1000x", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 ", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "x", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, " ", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1000 1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "1,000", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, " 1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + rv = env.rpc("channel_verify", pkAsHex, chan1Str, "", sig); + BEAST_EXPECT(rv[jss::error] == "channelAmtMalformed"); + } + { + // malformed channel + auto const pkAsHex = sliceToHex(pk.slice()); + auto chan1StrBad = chan1Str; + chan1StrBad.pop_back(); + auto rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + rv = env.rpc ("channel_authorize", "alice", chan1StrBad, "1000"); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + + chan1StrBad = chan1Str; + chan1StrBad.push_back('0'); + rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + rv = env.rpc ("channel_authorize", "alice", chan1StrBad, "1000"); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + + chan1StrBad = chan1Str; + chan1StrBad.back() = 'x'; + rv = env.rpc("channel_verify", pkAsHex, chan1StrBad, "1000", sig); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + rv = env.rpc ("channel_authorize", "alice", chan1StrBad, "1000"); + BEAST_EXPECT(rv[jss::error] == "channelMalformed"); + } + { + // give an ill formed base 58 public key + auto illFormedPk = chan1PkStr.substr(0, chan1PkStr.size() - 1); + auto const rv = env.rpc( + "channel_verify", illFormedPk, chan1Str, "1000", sig); + BEAST_EXPECT(!rv[jss::result][jss::signature_verified].asBool()); + } + { + // give an ill formed hex public key + auto const pkAsHex = sliceToHex(pk.slice()); + auto illFormedPk = pkAsHex.substr(0, chan1PkStr.size() - 1); + auto const rv = env.rpc( + "channel_verify", illFormedPk, chan1Str, "1000", sig); + BEAST_EXPECT(!rv[jss::result][jss::signature_verified].asBool()); + } } { // Try to verify chan2 auth with chan1 key @@ -782,9 +866,29 @@ struct PayChan_test : public beast::unit_test::suite env.rpc ("channel_authorize", "alice", chan2Str, "1000"); auto const sig = rs[jss::result][jss::signature].asString (); BEAST_EXPECT (!sig.empty ()); - auto const rv = - env.rpc ("channel_verify", chan1PkStr, chan1Str, "1000", sig); - BEAST_EXPECT (!rv[jss::result][jss::signature_verified].asBool ()); + { + auto const rv = env.rpc( + "channel_verify", chan1PkStr, chan1Str, "1000", sig); + BEAST_EXPECT( + !rv[jss::result][jss::signature_verified].asBool()); + } + { + // use pk hex to verify + auto const pkAsHex = sliceToHex(pk.slice()); + auto const rv = env.rpc( + "channel_verify", pkAsHex, chan1Str, "1000", sig); + BEAST_EXPECT( + !rv[jss::result][jss::signature_verified].asBool()); + } + } + { + // send malformed amounts rpc requests + auto rs = env.rpc("channel_authorize", "alice", chan1Str, "1000x"); + BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); + rs = env.rpc("channel_authorize", "alice", chan1Str, "x1000"); + BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); + rs = env.rpc("channel_authorize", "alice", chan1Str, "x"); + BEAST_EXPECT(rs[jss::error] == "channelAmtMalformed"); } }