Skip to content

Commit ba98269

Browse files
committed
wallet2: fix store_to() and change_password()
Resolves #8932 and: 2. Not storing cache when new path is different from old in `store_to()` and 3. Detecting same path when new path contains entire string of old path in `store_to()` and 4. Changing your password / decrypting your keys (in this method or others) and providing a bad original password and getting no error and 5. Changing your password and storing to a new file
1 parent eac1b86 commit ba98269

File tree

7 files changed

+359
-42
lines changed

7 files changed

+359
-42
lines changed

src/wallet/wallet2.cpp

+74-28
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,24 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total)
10031003
return n_multisig_keys;
10041004
}
10051005

1006+
/**
1007+
* @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files
1008+
*
1009+
* @param keys_data_key the chacha key that encrypts wallet keys files
1010+
* @return crypto::chacha_key the chacha key that encrypts the wallet cache files
1011+
*/
1012+
crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key)
1013+
{
1014+
static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
1015+
1016+
crypto::chacha_key cache_key;
1017+
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
1018+
memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE);
1019+
cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
1020+
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key);
1021+
1022+
return cache_key;
1023+
}
10061024
//-----------------------------------------------------------------
10071025
} //namespace
10081026

@@ -4406,6 +4424,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
44064424
crypto::chacha_key key;
44074425
crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
44084426

4427+
// We use m_cache_key as a deterministic test to see if given key corresponds to original password
4428+
const crypto::chacha_key cache_key = derive_cache_key(key);
4429+
THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
4430+
44094431
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
44104432
{
44114433
account.encrypt_viewkey(key);
@@ -4630,11 +4652,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password)
46304652
m_account.decrypt_viewkey(key);
46314653
}
46324654

4633-
static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
4634-
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
4635-
memcpy(cache_key_data.data(), &key, HASH_SIZE);
4636-
cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
4637-
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key);
4655+
m_cache_key = derive_cache_key(key);
4656+
46384657
get_ringdb_key();
46394658
}
46404659
//----------------------------------------------------------------------------------------------------
@@ -4643,9 +4662,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_
46434662
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
46444663
decrypt_keys(original_password);
46454664
setup_keys(new_password);
4646-
rewrite(filename, new_password);
46474665
if (!filename.empty())
4648-
store();
4666+
store_to(filename, new_password, true); // force rewrite keys file to possible new location
46494667
}
46504668
//----------------------------------------------------------------------------------------------------
46514669
/*!
@@ -5151,6 +5169,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key)
51515169

51525170
void wallet2::decrypt_keys(const crypto::chacha_key &key)
51535171
{
5172+
// We use m_cache_key as a deterministic test to see if given key corresponds to original password
5173+
const crypto::chacha_key cache_key = derive_cache_key(key);
5174+
THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);
5175+
51545176
m_account.encrypt_viewkey(key);
51555177
m_account.decrypt_keys(key);
51565178
}
@@ -6311,22 +6333,32 @@ void wallet2::store()
63116333
store_to("", epee::wipeable_string());
63126334
}
63136335
//----------------------------------------------------------------------------------------------------
6314-
void wallet2::store_to(const std::string &path, const epee::wipeable_string &password)
6336+
void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys)
63156337
{
63166338
trim_hashchain();
63176339

6340+
const bool had_old_wallet_files = !m_wallet_file.empty();
6341+
THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error,
6342+
"Cannot resave wallet to current file since wallet was not loaded from file to begin with");
6343+
63186344
// if file is the same, we do:
6319-
// 1. save wallet to the *.new file
6320-
// 2. remove old wallet file
6321-
// 3. rename *.new to wallet_name
6345+
// 1. overwrite the keys file iff force_rewrite_keys is specified
6346+
// 2. save cache to the *.new file
6347+
// 3. rename *.new to wallet_name, replacing old cache file
6348+
// else we do:
6349+
// 1. prepare new file names with "path" variable
6350+
// 2. store new keys files
6351+
// 3. remove old keys file
6352+
// 4. store new cache file
6353+
// 5. remove old cache file
63226354

63236355
// handle if we want just store wallet state to current files (ex store() replacement);
6324-
bool same_file = true;
6325-
if (!path.empty())
6356+
bool same_file = had_old_wallet_files && path.empty();
6357+
if (had_old_wallet_files && !path.empty())
63266358
{
6327-
std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string();
6328-
size_t pos = canonical_path.find(path);
6329-
same_file = pos != std::string::npos;
6359+
const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string();
6360+
const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string();
6361+
same_file = canonical_old_path == canonical_new_path;
63306362
}
63316363

63326364

@@ -6347,7 +6379,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
63476379
}
63486380

63496381
// get wallet cache data
6350-
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password);
6382+
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data();
63516383
THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data");
63526384

63536385
const std::string new_file = same_file ? m_wallet_file + ".new" : path;
@@ -6356,12 +6388,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
63566388
const std::string old_address_file = m_wallet_file + ".address.txt";
63576389
const std::string old_mms_file = m_mms_file;
63586390

6359-
// save keys to the new file
6360-
// if we here, main wallet file is saved and we only need to save keys and address files
6361-
if (!same_file) {
6391+
if (!same_file)
6392+
{
63626393
prepare_file_names(path);
6394+
}
6395+
6396+
if (!same_file || force_rewrite_keys)
6397+
{
63636398
bool r = store_keys(m_keys_file, password, false);
63646399
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
6400+
}
6401+
6402+
if (!same_file && had_old_wallet_files)
6403+
{
6404+
bool r = false;
63656405
if (boost::filesystem::exists(old_address_file))
63666406
{
63676407
// save address to the new file
@@ -6374,11 +6414,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
63746414
LOG_ERROR("error removing file: " << old_address_file);
63756415
}
63766416
}
6377-
// remove old wallet file
6378-
r = boost::filesystem::remove(old_file);
6379-
if (!r) {
6380-
LOG_ERROR("error removing file: " << old_file);
6381-
}
63826417
// remove old keys file
63836418
r = boost::filesystem::remove(old_keys_file);
63846419
if (!r) {
@@ -6392,8 +6427,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
63926427
LOG_ERROR("error removing file: " << old_mms_file);
63936428
}
63946429
}
6395-
} else {
6396-
// save to new file
6430+
}
6431+
6432+
// Save cache to new file. If storing to the same file, the temp path has the ".new" extension
63976433
#ifdef WIN32
63986434
// On Windows avoid using std::ofstream which does not work with UTF-8 filenames
63996435
// The price to pay is temporary higher memory consumption for string stream + binary archive
@@ -6413,10 +6449,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
64136449
THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file);
64146450
#endif
64156451

6452+
if (same_file)
6453+
{
64166454
// here we have "*.new" file, we need to rename it to be without ".new"
64176455
std::error_code e = tools::replace_file(new_file, m_wallet_file);
64186456
THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e);
64196457
}
6458+
else if (!same_file && had_old_wallet_files)
6459+
{
6460+
// remove old wallet file
6461+
bool r = boost::filesystem::remove(old_file);
6462+
if (!r) {
6463+
LOG_ERROR("error removing file: " << old_file);
6464+
}
6465+
}
64206466

64216467
if (m_message_store.get_active())
64226468
{
@@ -6426,7 +6472,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
64266472
}
64276473
}
64286474
//----------------------------------------------------------------------------------------------------
6429-
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords)
6475+
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
64306476
{
64316477
trim_hashchain();
64326478
try

src/wallet/wallet2.h

+16-6
Original file line numberDiff line numberDiff line change
@@ -940,22 +940,32 @@ namespace tools
940940
/*!
941941
* \brief store_to Stores wallet to another file(s), deleting old ones
942942
* \param path Path to the wallet file (keys and address filenames will be generated based on this filename)
943-
* \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?)
943+
* \param password Password that currently locks the wallet
944+
* \param force_rewrite_keys if true, always rewrite keys file
945+
*
946+
* Leave both "path" and "password" blank to restore the cache file to the current position in the disk
947+
* (which is the same as calling `store()`). If you want to store the wallet with a new password,
948+
* use the method `change_password()`.
949+
*
950+
* Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true
951+
* or when `path` is a new wallet file.
952+
*
953+
* \throw error::invalid_password If storing keys file and old password is incorrect
944954
*/
945-
void store_to(const std::string &path, const epee::wipeable_string &password);
955+
void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false);
946956
/*!
947957
* \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file.
948-
* \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?)
958+
* \param password Password that currently locks the wallet
949959
* \param watch_only true to include only view key, false to include both spend and view keys
950960
* \return Encrypted wallet keys data which can be stored to a wallet file
961+
* \throw error::invalid_password if password does not match current wallet
951962
*/
952963
boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only);
953964
/*!
954965
* \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file.
955-
* \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?)
956-
* \return Encrypted wallet cache data which can be stored to a wallet file
966+
* \return Encrypted wallet cache data which can be stored to a wallet file (using current password)
957967
*/
958-
boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password);
968+
boost::optional<wallet2::cache_file_data> get_cache_file_data();
959969

960970
std::string path() const;
961971

tests/CMakeLists.txt

+2-8
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,8 @@ else ()
7272
include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/gtest/include")
7373
endif (GTest_FOUND)
7474

75-
file(COPY
76-
data/wallet_9svHk1.keys
77-
data/wallet_9svHk1
78-
data/outputs
79-
data/unsigned_monero_tx
80-
data/signed_monero_tx
81-
data/sha256sum
82-
DESTINATION data)
75+
message(STATUS "Copying test data directory...")
76+
file(COPY data DESTINATION .) # Copy data directory from source root to build root
8377

8478
if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ)
8579
add_subdirectory(fuzz)

tests/data/wallet_00fd416a

2.22 MB
Binary file not shown.

tests/data/wallet_00fd416a.keys

1.64 KB
Binary file not shown.

tests/unit_tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ set(unit_tests_sources
9797
output_selection.cpp
9898
vercmp.cpp
9999
ringdb.cpp
100+
wallet_storage.cpp
100101
wipeable_string.cpp
101102
is_hdd.cpp
102103
aligned.cpp

0 commit comments

Comments
 (0)