Merge #15235: Do not import private keys to wallets with private keys disabled

e6c58d3b014ab8ef5cca4be68764af4b79685fcb Do not import private keys to wallets with private keys disabled (Andrew Chow)
b5c5021b644731d14a6ef04961320a99466f035a Refactor importwallet to extract data from the file and then import (Andrew Chow)
1f77f6754ce724493b0cb084ae0b35107d58605f tests: unify RPC argument to cli argument conversion and handle dicts and lists (Andrew Chow)

Pull request description:

  Fixes a bug where private keys could be imported to wallets with private keys disabled. Now every RPC which can import private keys checks for whether the wallet has private keys are disabled and errors if it is. Also added an belt-and-suspenders check to `AddKeyPubkeyWithDB` to have it assert that the wallet has private keys enabled.

Tree-SHA512: 5cd04febce9aa2bd9bfd02f312c6ff8705e37278cae59efd3895f6d6e2f1b477aefd297e2dd0860791bdd3d4f3cad8eb1a404f8f3d4e2035b91314ad2c1028ae

dash changes
This commit is contained in:
Wladimir J. van der Laan 2019-02-01 13:39:43 +01:00 committed by pbattu
parent 68dccf4d3a
commit ac01dbee63
5 changed files with 98 additions and 33 deletions

View File

@ -109,6 +109,9 @@ UniValue importprivkey(const JSONRPCRequest& request)
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
); );
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
}
WalletRescanReserver reserver(pwallet); WalletRescanReserver reserver(pwallet);
bool fRescan = true; bool fRescan = true;
@ -525,8 +528,10 @@ UniValue importwallet(const JSONRPCRequest& request)
// Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
// we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
std::vector<std::pair<CScript, int64_t>> scripts;
while (file.good()) { while (file.good()) {
uiInterface.ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); uiInterface.ShowProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
std::string line; std::string line;
std::getline(file, line); std::getline(file, line);
if (line.empty() || line[0] == '#') if (line.empty() || line[0] == '#')
@ -538,13 +543,6 @@ UniValue importwallet(const JSONRPCRequest& request)
continue; continue;
CKey key = DecodeSecret(vstr[0]); CKey key = DecodeSecret(vstr[0]);
if (key.IsValid()) { if (key.IsValid()) {
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID keyid = pubkey.GetID();
if (pwallet->HaveKey(keyid)) {
pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
continue;
}
int64_t nTime = DecodeDumpTime(vstr[1]); int64_t nTime = DecodeDumpTime(vstr[1]);
std::string strLabel; std::string strLabel;
bool fLabel = true; bool fLabel = true;
@ -560,36 +558,67 @@ UniValue importwallet(const JSONRPCRequest& request)
fLabel = true; fLabel = true;
} }
} }
keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel));
} else if(IsHex(vstr[0])) {
std::vector<unsigned char> vData(ParseHex(vstr[0]));
CScript script = CScript(vData.begin(), vData.end());
int64_t birth_time = DecodeDumpTime(vstr[1]);
scripts.push_back(std::pair<CScript, int64_t>(script, birth_time));
}
}
file.close();
// We now know whether we are importing private keys, so we can error if private keys are disabled
if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled");
}
double total = (double)(keys.size() + scripts.size());
double progress = 0;
for (const auto& key_tuple : keys) {
uiInterface.ShowProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
const CKey& key = std::get<0>(key_tuple);
int64_t time = std::get<1>(key_tuple);
bool has_label = std::get<2>(key_tuple);
std::string label = std::get<3>(key_tuple);
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID keyid = pubkey.GetID();
if (pwallet->HaveKey(keyid)) {
pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
continue;
}
pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid)); pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid));
if (!pwallet->AddKeyPubKey(key, pubkey)) { if (!pwallet->AddKeyPubKey(key, pubkey)) {
fGood = false; fGood = false;
continue; continue;
} }
pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; pwallet->mapKeyMetadata[keyid].nCreateTime = time;
if (fLabel) if (has_label)
pwallet->SetAddressBook(keyid, strLabel, "receive"); pwallet->SetAddressBook(keyid, label, "receive");
nTimeBegin = std::min(nTimeBegin, nTime); nTimeBegin = std::min(nTimeBegin, time);
} else if(IsHex(vstr[0])) { progress++;
std::vector<unsigned char> vData(ParseHex(vstr[0])); }
CScript script = CScript(vData.begin(), vData.end()); for (const auto& script_pair : scripts) {
uiInterface.ShowProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
const CScript& script = script_pair.first;
int64_t time = script_pair.second;
CScriptID id(script); CScriptID id(script);
if (pwallet->HaveCScript(id)) { if (pwallet->HaveCScript(id)) {
pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", vstr[0]); pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", HexStr(script));
continue; continue;
} }
if(!pwallet->AddCScript(script)) { if(!pwallet->AddCScript(script)) {
pwallet->WalletLogPrintf("Error importing script %s\n", vstr[0]); pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script));
fGood = false; fGood = false;
continue; continue;
} }
int64_t birth_time = DecodeDumpTime(vstr[1]); if (time > 0) {
if (birth_time > 0) { pwallet->m_script_metadata[id].nCreateTime = time;
pwallet->m_script_metadata[id].nCreateTime = birth_time; nTimeBegin = std::min(nTimeBegin, time);
nTimeBegin = std::min(nTimeBegin, birth_time);
} }
progress++;
} }
}
file.close();
uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
pwallet->UpdateTimeFirstKey(nTimeBegin); pwallet->UpdateTimeFirstKey(nTimeBegin);
} }
@ -629,6 +658,10 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
if (fPruneMode) if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
@ -1020,6 +1053,12 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
// Parse the output. // Parse the output.
// If private keys are disabled, abort if private keys are being imported
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.isNull()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
}
// Generate the script and destination for the scriptPubKey provided
CScript script; CScript script;
CTxDestination dest; CTxDestination dest;

View File

@ -3181,6 +3181,10 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot downgrade wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Cannot downgrade wallet");
} }
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
bool prev_encrypted = pwallet->IsCrypted(); bool prev_encrypted = pwallet->IsCrypted();
SecureString secureWalletPassphrase; SecureString secureWalletPassphrase;

View File

@ -359,6 +359,9 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C
{ {
AssertLockHeld(cs_wallet); // mapKeyMetadata AssertLockHeld(cs_wallet); // mapKeyMetadata
// Make sure we aren't adding private keys to private key disabled wallets
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
// CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey
// which is overridden below. To avoid flushes, the database handle is // which is overridden below. To avoid flushes, the database handle is
// tunneled through to it. // tunneled through to it.

View File

@ -375,6 +375,14 @@ class TestNodeCLIAttr:
def get_request(self, *args, **kwargs): def get_request(self, *args, **kwargs):
return lambda: self(*args, **kwargs) return lambda: self(*args, **kwargs)
def arg_to_cli(arg):
if isinstance(arg, bool):
return str(arg).lower()
elif isinstance(arg, dict) or isinstance(arg, list):
return json.dumps(arg)
else:
return str(arg)
class TestNodeCLI(): class TestNodeCLI():
"""Interface to dash-cli for an individual node""" """Interface to dash-cli for an individual node"""
@ -406,8 +414,8 @@ class TestNodeCLI():
def send_cli(self, command=None, *args, **kwargs): def send_cli(self, command=None, *args, **kwargs):
"""Run dash-cli command. Deserializes returned string as python object.""" """Run dash-cli command. Deserializes returned string as python object."""
pos_args = [str(arg).lower() if type(arg) is bool else str(arg) for arg in args] pos_args = [arg_to_cli(arg) for arg in args]
named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()] named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()]
assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same dash-cli call" assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same dash-cli call"
p_args = [self.binary, "-datadir=" + self.datadir] + self.options p_args = [self.binary, "-datadir=" + self.datadir] + self.options
if named_args: if named_args:

View File

@ -7,6 +7,7 @@
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_equal,
assert_raises_rpc_error, assert_raises_rpc_error,
) )
@ -28,5 +29,15 @@ class DisablePrivateKeysTest(BitcoinTestFramework):
assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress) assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey']) w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey'])
self.log.info('Test that private keys cannot be imported')
addr = w2.getnewaddress('')
privkey = w2.dumpprivkey(addr)
assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
assert(not result[0]['success'])
assert('warning' not in result[0])
assert_equal(result[0]['error']['code'], -4)
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
if __name__ == '__main__': if __name__ == '__main__':
DisablePrivateKeysTest().main() DisablePrivateKeysTest().main()