Skip to content

Commit

Permalink
wallet2: fix store_to() and change_password()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jeffro256 committed Aug 23, 2023
1 parent 8123d94 commit 1bea8ef
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 42 deletions.
102 changes: 74 additions & 28 deletions src/wallet/wallet2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,24 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total)
return n_multisig_keys;
}

/**
* @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files
*
* @param keys_data_key the chacha key that encrypts wallet keys files
* @return crypto::chacha_key the chacha key that encrypts the wallet cache files
*/
crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key)
{
static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");

crypto::chacha_key cache_key;
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE);
cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key);

return cache_key;
}
//-----------------------------------------------------------------
} //namespace

Expand Down Expand Up @@ -4348,6 +4366,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
crypto::chacha_key key;
crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);

// We use m_cache_key as a deterministic test to see if given key corresponds to original password
const crypto::chacha_key cache_key = derive_cache_key(key);
THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);

if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
{
account.encrypt_viewkey(key);
Expand Down Expand Up @@ -4575,11 +4597,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password)
m_account.decrypt_viewkey(key);
}

static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data;
memcpy(cache_key_data.data(), &key, HASH_SIZE);
cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE;
cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key);
m_cache_key = derive_cache_key(key);

get_ringdb_key();
}
//----------------------------------------------------------------------------------------------------
Expand All @@ -4588,9 +4607,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_
if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only)
decrypt_keys(original_password);
setup_keys(new_password);
rewrite(filename, new_password);
if (!filename.empty())
store();
store_to(filename, new_password, true); // force rewrite keys file to possible new location
}
//----------------------------------------------------------------------------------------------------
/*!
Expand Down Expand Up @@ -5083,6 +5101,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key)

void wallet2::decrypt_keys(const crypto::chacha_key &key)
{
// We use m_cache_key as a deterministic test to see if given key corresponds to original password
const crypto::chacha_key cache_key = derive_cache_key(key);
THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password);

m_account.encrypt_viewkey(key);
m_account.decrypt_keys(key);
}
Expand Down Expand Up @@ -6230,22 +6252,32 @@ void wallet2::store()
store_to("", epee::wipeable_string());
}
//----------------------------------------------------------------------------------------------------
void wallet2::store_to(const std::string &path, const epee::wipeable_string &password)
void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys)
{
trim_hashchain();

const bool had_old_wallet_files = !m_wallet_file.empty();
THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error,
"Cannot resave wallet to current file since wallet was not loaded from file to begin with");

// if file is the same, we do:
// 1. save wallet to the *.new file
// 2. remove old wallet file
// 3. rename *.new to wallet_name
// 1. overwrite the keys file iff force_rewrite_keys is specified
// 2. save cache to the *.new file
// 3. rename *.new to wallet_name, replacing old cache file
// else we do:
// 1. prepare new file names with "path" variable
// 2. store new keys files
// 3. remove old keys file
// 4. store new cache file
// 5. remove old cache file

// handle if we want just store wallet state to current files (ex store() replacement);
bool same_file = true;
if (!path.empty())
bool same_file = had_old_wallet_files && path.empty();
if (had_old_wallet_files && !path.empty())
{
std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string();
size_t pos = canonical_path.find(path);
same_file = pos != std::string::npos;
const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string();
const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string();
same_file = canonical_old_path == canonical_new_path;
}


Expand All @@ -6266,7 +6298,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
}

// get wallet cache data
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password);
boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data();
THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data");

const std::string new_file = same_file ? m_wallet_file + ".new" : path;
Expand All @@ -6275,12 +6307,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
const std::string old_address_file = m_wallet_file + ".address.txt";
const std::string old_mms_file = m_mms_file;

// save keys to the new file
// if we here, main wallet file is saved and we only need to save keys and address files
if (!same_file) {
if (!same_file)
{
prepare_file_names(path);
}

if (!same_file || force_rewrite_keys)
{
bool r = store_keys(m_keys_file, password, false);
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
}

if (!same_file && had_old_wallet_files)
{
bool r = false;
if (boost::filesystem::exists(old_address_file))
{
// save address to the new file
Expand All @@ -6293,11 +6333,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
LOG_ERROR("error removing file: " << old_address_file);
}
}
// remove old wallet file
r = boost::filesystem::remove(old_file);
if (!r) {
LOG_ERROR("error removing file: " << old_file);
}
// remove old keys file
r = boost::filesystem::remove(old_keys_file);
if (!r) {
Expand All @@ -6311,8 +6346,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
LOG_ERROR("error removing file: " << old_mms_file);
}
}
} else {
// save to new file
}

// Save cache to new file. If storing to the same file, the temp path has the ".new" extension
#ifdef WIN32
// On Windows avoid using std::ofstream which does not work with UTF-8 filenames
// The price to pay is temporary higher memory consumption for string stream + binary archive
Expand All @@ -6332,10 +6368,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file);
#endif

if (same_file)
{
// here we have "*.new" file, we need to rename it to be without ".new"
std::error_code e = tools::replace_file(new_file, m_wallet_file);
THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e);
}
else if (!same_file && had_old_wallet_files)
{
// remove old wallet file
bool r = boost::filesystem::remove(old_file);
if (!r) {
LOG_ERROR("error removing file: " << old_file);
}
}

if (m_message_store.get_active())
{
Expand All @@ -6345,7 +6391,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
}
}
//----------------------------------------------------------------------------------------------------
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords)
boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data()
{
trim_hashchain();
try
Expand Down
22 changes: 16 additions & 6 deletions src/wallet/wallet2.h
Original file line number Diff line number Diff line change
Expand Up @@ -933,22 +933,32 @@ namespace tools
/*!
* \brief store_to Stores wallet to another file(s), deleting old ones
* \param path Path to the wallet file (keys and address filenames will be generated based on this filename)
* \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?)
* \param password Password that currently locks the wallet
* \param force_rewrite_keys if true, always rewrite keys file
*
* Leave both "path" and "password" blank to restore the cache file to the current position in the disk
* (which is the same as calling `store()`). If you want to store the wallet with a new password,
* use the method `change_password()`.
*
* Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true
* or when `path` is a new wallet file.
*
* \throw error::invalid_password If storing keys file and old password is incorrect
*/
void store_to(const std::string &path, const epee::wipeable_string &password);
void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false);
/*!
* \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file.
* \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?)
* \param password Password that currently locks the wallet
* \param watch_only true to include only view key, false to include both spend and view keys
* \return Encrypted wallet keys data which can be stored to a wallet file
* \throw error::invalid_password if password does not match current wallet
*/
boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only);
/*!
* \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file.
* \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?)
* \return Encrypted wallet cache data which can be stored to a wallet file
* \return Encrypted wallet cache data which can be stored to a wallet file (using current password)
*/
boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password);
boost::optional<wallet2::cache_file_data> get_cache_file_data();

std::string path() const;

Expand Down
10 changes: 2 additions & 8 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,8 @@ else ()
include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/gtest/include")
endif (GTest_FOUND)

file(COPY
data/wallet_9svHk1.keys
data/wallet_9svHk1
data/outputs
data/unsigned_monero_tx
data/signed_monero_tx
data/sha256sum
DESTINATION data)
message(STATUS "Copying test data directory...")
file(COPY data DESTINATION .) # Copy data directory from source root to build root

if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ)
add_subdirectory(fuzz)
Expand Down
Binary file added tests/data/wallet_00fd416a
Binary file not shown.
Binary file added tests/data/wallet_00fd416a.keys
Binary file not shown.
1 change: 1 addition & 0 deletions tests/unit_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ set(unit_tests_sources
output_selection.cpp
vercmp.cpp
ringdb.cpp
wallet_storage.cpp
wipeable_string.cpp
is_hdd.cpp
aligned.cpp
Expand Down
Loading

0 comments on commit 1bea8ef

Please sign in to comment.