Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet2: fix store_to() and change_password() [RELEASE] #8938

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 74 additions & 28 deletions src/wallet/wallet2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,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 @@ -4406,6 +4424,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 @@ -4630,11 +4652,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 @@ -4643,9 +4662,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 @@ -5151,6 +5169,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 @@ -6311,22 +6333,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 @@ -6347,7 +6379,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 @@ -6356,12 +6388,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 @@ -6374,11 +6414,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 @@ -6392,8 +6427,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 @@ -6413,10 +6449,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 @@ -6426,7 +6472,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 @@ -940,22 +940,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 @@ -97,6 +97,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