wallet: wipe seed from memory where appropriate

This commit is contained in:
moneromooo-monero 2018-07-07 00:03:15 +01:00
parent b780cf4db1
commit ea37614efe
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3
19 changed files with 653 additions and 144 deletions

View File

@ -0,0 +1,45 @@
// Copyright (c) 2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
namespace epee
{
namespace fnv
{
inline uint64_t FNV1a(const char *ptr, size_t sz)
{
uint64_t h = 0xcbf29ce484222325;
for (size_t i = 0; i < sz; ++i)
h = (h ^ *(const uint8_t*)ptr++) * 0x100000001b3;
return h;
}
}
}

View File

@ -33,6 +33,7 @@
#include <iosfwd> #include <iosfwd>
#include <string> #include <string>
#include "wipeable_string.h"
#include "span.h" #include "span.h"
namespace epee namespace epee
@ -41,6 +42,8 @@ namespace epee
{ {
//! \return A std::string containing hex of `src`. //! \return A std::string containing hex of `src`.
static std::string string(const span<const std::uint8_t> src); static std::string string(const span<const std::uint8_t> src);
//! \return A epee::wipeable_string containing hex of `src`.
static epee::wipeable_string wipeable_string(const span<const std::uint8_t> src);
//! \return An array containing hex of `src`. //! \return An array containing hex of `src`.
template<std::size_t N> template<std::size_t N>
@ -59,6 +62,8 @@ namespace epee
static void formatted(std::ostream& out, const span<const std::uint8_t> src); static void formatted(std::ostream& out, const span<const std::uint8_t> src);
private: private:
template<typename T> T static convert(const span<const std::uint8_t> src);
//! Write `src` bytes as hex to `out`. `out` must be twice the length //! Write `src` bytes as hex to `out`. `out` must be twice the length
static void buffer_unchecked(char* out, const span<const std::uint8_t> src) noexcept; static void buffer_unchecked(char* out, const span<const std::uint8_t> src) noexcept;
}; };

View File

@ -28,28 +28,43 @@
#pragma once #pragma once
#include <boost/optional/optional_fwd.hpp>
#include <stddef.h> #include <stddef.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include "fnv1.h"
namespace epee namespace epee
{ {
class wipeable_string class wipeable_string
{ {
public: public:
typedef char value_type;
wipeable_string() {} wipeable_string() {}
wipeable_string(const wipeable_string &other); wipeable_string(const wipeable_string &other);
wipeable_string(wipeable_string &&other); wipeable_string(wipeable_string &&other);
wipeable_string(const std::string &other); wipeable_string(const std::string &other);
wipeable_string(std::string &&other); wipeable_string(std::string &&other);
wipeable_string(const char *s); wipeable_string(const char *s);
wipeable_string(const char *s, size_t len);
~wipeable_string(); ~wipeable_string();
void wipe(); void wipe();
void push_back(char c); void push_back(char c);
void pop_back(); void operator+=(char c);
void operator+=(const std::string &s);
void operator+=(const epee::wipeable_string &s);
void operator+=(const char *s);
void append(const char *ptr, size_t len);
char pop_back();
const char *data() const noexcept { return buffer.data(); } const char *data() const noexcept { return buffer.data(); }
char *data() noexcept { return buffer.data(); }
size_t size() const noexcept { return buffer.size(); } size_t size() const noexcept { return buffer.size(); }
size_t length() const noexcept { return buffer.size(); }
bool empty() const noexcept { return buffer.empty(); } bool empty() const noexcept { return buffer.empty(); }
void trim();
void split(std::vector<wipeable_string> &fields) const;
boost::optional<wipeable_string> parse_hexstr() const;
void resize(size_t sz); void resize(size_t sz);
void reserve(size_t sz); void reserve(size_t sz);
void clear(); void clear();
@ -65,3 +80,14 @@ namespace epee
std::vector<char> buffer; std::vector<char> buffer;
}; };
} }
namespace std
{
template<> struct hash<epee::wipeable_string>
{
size_t operator()(const epee::wipeable_string &s) const
{
return epee::fnv::FNV1a(s.data(), s.size());
}
};
}

View File

@ -52,17 +52,21 @@ namespace epee
} }
} }
std::string to_hex::string(const span<const std::uint8_t> src) template<typename T>
T to_hex::convert(const span<const std::uint8_t> src)
{ {
if (std::numeric_limits<std::size_t>::max() / 2 < src.size()) if (std::numeric_limits<std::size_t>::max() / 2 < src.size())
throw std::range_error("hex_view::to_string exceeded maximum size"); throw std::range_error("hex_view::to_string exceeded maximum size");
std::string out{}; T out{};
out.resize(src.size() * 2); out.resize(src.size() * 2);
buffer_unchecked(std::addressof(out[0]), src); to_hex::buffer_unchecked((char*)out.data(), src); // can't see the non const version in wipeable_string??
return out; return out;
} }
std::string to_hex::string(const span<const std::uint8_t> src) { return convert<std::string>(src); }
epee::wipeable_string to_hex::wipeable_string(const span<const std::uint8_t> src) { return convert<epee::wipeable_string>(src); }
void to_hex::buffer(std::ostream& out, const span<const std::uint8_t> src) void to_hex::buffer(std::ostream& out, const span<const std::uint8_t> src)
{ {
write_hex(std::ostreambuf_iterator<char>{out}, src); write_hex(std::ostreambuf_iterator<char>{out}, src);

View File

@ -26,11 +26,22 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/optional/optional.hpp>
#include <string.h> #include <string.h>
#include "memwipe.h" #include "memwipe.h"
#include "misc_log_ex.h" #include "misc_log_ex.h"
#include "wipeable_string.h" #include "wipeable_string.h"
namespace
{
int atolower(int c)
{
if (c >= 'A' && c <= 'Z')
c |= 32;
return c;
}
}
namespace epee namespace epee
{ {
@ -69,6 +80,12 @@ wipeable_string::wipeable_string(const char *s)
memcpy(buffer.data(), s, size()); memcpy(buffer.data(), s, size());
} }
wipeable_string::wipeable_string(const char *s, size_t len)
{
grow(len);
memcpy(buffer.data(), s, len);
}
wipeable_string::~wipeable_string() wipeable_string::~wipeable_string()
{ {
wipe(); wipe();
@ -109,9 +126,100 @@ void wipeable_string::push_back(char c)
buffer.back() = c; buffer.back() = c;
} }
void wipeable_string::pop_back() void wipeable_string::operator+=(char c)
{ {
resize(size() - 1); push_back(c);
}
void wipeable_string::append(const char *ptr, size_t len)
{
const size_t orgsz = size();
CHECK_AND_ASSERT_THROW_MES(orgsz < std::numeric_limits<size_t>::max() - len, "Appended data too large");
grow(orgsz + len);
if (len > 0)
memcpy(data() + orgsz, ptr, len);
}
void wipeable_string::operator+=(const char *s)
{
append(s, strlen(s));
}
void wipeable_string::operator+=(const epee::wipeable_string &s)
{
append(s.data(), s.size());
}
void wipeable_string::operator+=(const std::string &s)
{
append(s.c_str(), s.size());
}
void wipeable_string::trim()
{
size_t prefix = 0;
while (prefix < size() && data()[prefix] == ' ')
++prefix;
if (prefix > 0)
memmove(buffer.data(), buffer.data() + prefix, size() - prefix);
size_t suffix = 0;
while (suffix < size()-prefix && data()[size() - 1 - prefix - suffix] == ' ')
++suffix;
resize(size() - prefix - suffix);
}
void wipeable_string::split(std::vector<wipeable_string> &fields) const
{
fields.clear();
size_t len = size();
const char *ptr = data();
bool space = true;
while (len--)
{
const char c = *ptr++;
if (c != ' ')
{
if (space)
fields.push_back({});
fields.back().push_back(c);
}
space = c == ' ';
}
}
boost::optional<epee::wipeable_string> wipeable_string::parse_hexstr() const
{
if (size() % 2 != 0)
return boost::none;
boost::optional<epee::wipeable_string> res = epee::wipeable_string("");
const size_t len = size();
const char *d = data();
res->grow(0, len / 2);
static constexpr const char hex[] = u8"0123456789abcdef";
for (size_t i = 0; i < len; i += 2)
{
char c = atolower(d[i]);
const char *ptr0 = strchr(hex, c);
if (!ptr0)
return boost::none;
c = atolower(d[i+1]);
const char *ptr1 = strchr(hex, c);
if (!ptr1)
return boost::none;
res->push_back(((ptr0-hex)<<4) | (ptr1-hex));
}
return res;
}
char wipeable_string::pop_back()
{
const size_t sz = size();
CHECK_AND_ASSERT_THROW_MES(sz > 0, "Popping from an empty string");
const char c = buffer.back();
resize(sz - 1);
return c;
} }
void wipeable_string::resize(size_t sz) void wipeable_string::resize(size_t sz)

View File

@ -54,7 +54,7 @@ namespace
return 0 != _isatty(_fileno(stdin)); return 0 != _isatty(_fileno(stdin));
} }
bool read_from_tty(epee::wipeable_string& pass) bool read_from_tty(epee::wipeable_string& pass, bool hide_input)
{ {
static constexpr const char BACKSPACE = 8; static constexpr const char BACKSPACE = 8;
@ -62,7 +62,7 @@ namespace
DWORD mode_old; DWORD mode_old;
::GetConsoleMode(h_cin, &mode_old); ::GetConsoleMode(h_cin, &mode_old);
DWORD mode_new = mode_old & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); DWORD mode_new = mode_old & ~((hide_input ? ENABLE_ECHO_INPUT : 0) | ENABLE_LINE_INPUT);
::SetConsoleMode(h_cin, mode_new); ::SetConsoleMode(h_cin, mode_new);
bool r = true; bool r = true;
@ -107,14 +107,14 @@ namespace
return 0 != isatty(fileno(stdin)); return 0 != isatty(fileno(stdin));
} }
int getch() noexcept int getch(bool hide_input) noexcept
{ {
struct termios tty_old; struct termios tty_old;
tcgetattr(STDIN_FILENO, &tty_old); tcgetattr(STDIN_FILENO, &tty_old);
struct termios tty_new; struct termios tty_new;
tty_new = tty_old; tty_new = tty_old;
tty_new.c_lflag &= ~(ICANON | ECHO); tty_new.c_lflag &= ~(ICANON | (hide_input ? ECHO : 0));
tcsetattr(STDIN_FILENO, TCSANOW, &tty_new); tcsetattr(STDIN_FILENO, TCSANOW, &tty_new);
int ch = getchar(); int ch = getchar();
@ -124,14 +124,14 @@ namespace
return ch; return ch;
} }
bool read_from_tty(epee::wipeable_string& aPass) bool read_from_tty(epee::wipeable_string& aPass, bool hide_input)
{ {
static constexpr const char BACKSPACE = 127; static constexpr const char BACKSPACE = 127;
aPass.reserve(tools::password_container::max_password_size); aPass.reserve(tools::password_container::max_password_size);
while (aPass.size() < tools::password_container::max_password_size) while (aPass.size() < tools::password_container::max_password_size)
{ {
int ch = getch(); int ch = getch(hide_input);
if (EOF == ch || ch == EOT) if (EOF == ch || ch == EOT)
{ {
return false; return false;
@ -159,18 +159,18 @@ namespace
#endif // end !WIN32 #endif // end !WIN32
bool read_from_tty(const bool verify, const char *message, epee::wipeable_string& pass1, epee::wipeable_string& pass2) bool read_from_tty(const bool verify, const char *message, bool hide_input, epee::wipeable_string& pass1, epee::wipeable_string& pass2)
{ {
while (true) while (true)
{ {
if (message) if (message)
std::cout << message <<": " << std::flush; std::cout << message <<": " << std::flush;
if (!read_from_tty(pass1)) if (!read_from_tty(pass1, hide_input))
return false; return false;
if (verify) if (verify)
{ {
std::cout << "Confirm password: "; std::cout << "Confirm password: ";
if (!read_from_tty(pass2)) if (!read_from_tty(pass2, hide_input))
return false; return false;
if(pass1!=pass2) if(pass1!=pass2)
{ {
@ -229,12 +229,12 @@ namespace tools
std::atomic<bool> password_container::is_prompting(false); std::atomic<bool> password_container::is_prompting(false);
boost::optional<password_container> password_container::prompt(const bool verify, const char *message) boost::optional<password_container> password_container::prompt(const bool verify, const char *message, bool hide_input)
{ {
is_prompting = true; is_prompting = true;
password_container pass1{}; password_container pass1{};
password_container pass2{}; password_container pass2{};
if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) if (is_cin_tty() ? read_from_tty(verify, message, hide_input, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password))
{ {
is_prompting = false; is_prompting = false;
return {std::move(pass1)}; return {std::move(pass1)};

View File

@ -49,7 +49,7 @@ namespace tools
password_container(std::string&& password) noexcept; password_container(std::string&& password) noexcept;
//! \return A password from stdin TTY prompt or `std::cin` pipe. //! \return A password from stdin TTY prompt or `std::cin` pipe.
static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password"); static boost::optional<password_container> prompt(bool verify, const char *mesage = "Password", bool hide_input = true);
static std::atomic<bool> is_prompting; static std::atomic<bool> is_prompting;
password_container(const password_container&) = delete; password_container(const password_container&) = delete;

View File

@ -43,6 +43,8 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include "wipeable_string.h"
#include "misc_language.h"
#include "crypto/crypto.h" // for declaration of crypto::secret_key #include "crypto/crypto.h" // for declaration of crypto::secret_key
#include <fstream> #include <fstream>
#include "mnemonics/electrum-words.h" #include "mnemonics/electrum-words.h"
@ -80,9 +82,9 @@ namespace crypto
namespace namespace
{ {
uint32_t create_checksum_index(const std::vector<std::string> &word_list, uint32_t create_checksum_index(const std::vector<epee::wipeable_string> &word_list,
uint32_t unique_prefix_length); uint32_t unique_prefix_length);
bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length); bool checksum_test(std::vector<epee::wipeable_string> seed, uint32_t unique_prefix_length);
/*! /*!
* \brief Finds the word list that contains the seed words and puts the indices * \brief Finds the word list that contains the seed words and puts the indices
@ -93,7 +95,7 @@ namespace
* \param language Language instance pointer to write to after it is found. * \param language Language instance pointer to write to after it is found.
* \return true if all the words were present in some language false if not. * \return true if all the words were present in some language false if not.
*/ */
bool find_seed_language(const std::vector<std::string> &seed, bool find_seed_language(const std::vector<epee::wipeable_string> &seed,
bool has_checksum, std::vector<uint32_t> &matched_indices, Language::Base **language) bool has_checksum, std::vector<uint32_t> &matched_indices, Language::Base **language)
{ {
// If there's a new language added, add an instance of it here. // If there's a new language added, add an instance of it here.
@ -114,17 +116,19 @@ namespace
}); });
Language::Base *fallback = NULL; Language::Base *fallback = NULL;
std::vector<epee::wipeable_string>::const_iterator it2;
matched_indices.reserve(seed.size());
// Iterate through all the languages and find a match // Iterate through all the languages and find a match
for (std::vector<Language::Base*>::iterator it1 = language_instances.begin(); for (std::vector<Language::Base*>::iterator it1 = language_instances.begin();
it1 != language_instances.end(); it1++) it1 != language_instances.end(); it1++)
{ {
const std::unordered_map<std::string, uint32_t> &word_map = (*it1)->get_word_map(); const std::unordered_map<epee::wipeable_string, uint32_t> &word_map = (*it1)->get_word_map();
const std::unordered_map<std::string, uint32_t> &trimmed_word_map = (*it1)->get_trimmed_word_map(); const std::unordered_map<epee::wipeable_string, uint32_t> &trimmed_word_map = (*it1)->get_trimmed_word_map();
// To iterate through seed words // To iterate through seed words
std::vector<std::string>::const_iterator it2;
bool full_match = true; bool full_match = true;
std::string trimmed_word; epee::wipeable_string trimmed_word;
// Iterate through all the words and see if they're all present // Iterate through all the words and see if they're all present
for (it2 = seed.begin(); it2 != seed.end(); it2++) for (it2 = seed.begin(); it2 != seed.end(); it2++)
{ {
@ -167,6 +171,7 @@ namespace
return true; return true;
} }
// Some didn't match. Clear the index array. // Some didn't match. Clear the index array.
memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0]));
matched_indices.clear(); matched_indices.clear();
} }
@ -181,6 +186,7 @@ namespace
} }
MINFO("No match found"); MINFO("No match found");
memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0]));
return false; return false;
} }
@ -190,12 +196,12 @@ namespace
* \param unique_prefix_length the prefix length of each word to use for checksum * \param unique_prefix_length the prefix length of each word to use for checksum
* \return Checksum index * \return Checksum index
*/ */
uint32_t create_checksum_index(const std::vector<std::string> &word_list, uint32_t create_checksum_index(const std::vector<epee::wipeable_string> &word_list,
uint32_t unique_prefix_length) uint32_t unique_prefix_length)
{ {
std::string trimmed_words = ""; epee::wipeable_string trimmed_words = "";
for (std::vector<std::string>::const_iterator it = word_list.begin(); it != word_list.end(); it++) for (std::vector<epee::wipeable_string>::const_iterator it = word_list.begin(); it != word_list.end(); it++)
{ {
if (it->length() > unique_prefix_length) if (it->length() > unique_prefix_length)
{ {
@ -217,22 +223,22 @@ namespace
* \param unique_prefix_length the prefix length of each word to use for checksum * \param unique_prefix_length the prefix length of each word to use for checksum
* \return True if the test passed false if not. * \return True if the test passed false if not.
*/ */
bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length) bool checksum_test(std::vector<epee::wipeable_string> seed, uint32_t unique_prefix_length)
{ {
if (seed.empty()) if (seed.empty())
return false; return false;
// The last word is the checksum. // The last word is the checksum.
std::string last_word = seed.back(); epee::wipeable_string last_word = seed.back();
seed.pop_back(); seed.pop_back();
std::string checksum = seed[create_checksum_index(seed, unique_prefix_length)]; epee::wipeable_string checksum = seed[create_checksum_index(seed, unique_prefix_length)];
std::string trimmed_checksum = checksum.length() > unique_prefix_length ? Language::utf8prefix(checksum, unique_prefix_length) : epee::wipeable_string trimmed_checksum = checksum.length() > unique_prefix_length ? Language::utf8prefix(checksum, unique_prefix_length) :
checksum; checksum;
std::string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) : epee::wipeable_string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) :
last_word; last_word;
bool ret = trimmed_checksum == trimmed_last_word; bool ret = trimmed_checksum == trimmed_last_word;
MINFO("Checksum is %s" << (ret ? "valid" : "invalid")); MINFO("Checksum is " << (ret ? "valid" : "invalid"));
return ret; return ret;
} }
} }
@ -260,13 +266,12 @@ namespace crypto
* \param language_name Language of the seed as found gets written here. * \param language_name Language of the seed as found gets written here.
* \return false if not a multiple of 3 words, or if word is not in the words list * \return false if not a multiple of 3 words, or if word is not in the words list
*/ */
bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, bool words_to_bytes(const epee::wipeable_string &words, epee::wipeable_string& dst, size_t len, bool duplicate,
std::string &language_name) std::string &language_name)
{ {
std::vector<std::string> seed; std::vector<epee::wipeable_string> seed;
boost::algorithm::trim(words); words.split(seed);
boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on);
if (len % 4) if (len % 4)
{ {
@ -291,6 +296,7 @@ namespace crypto
} }
std::vector<uint32_t> matched_indices; std::vector<uint32_t> matched_indices;
auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(matched_indices.data(), matched_indices.size() * sizeof(matched_indices[0]));});
Language::Base *language; Language::Base *language;
if (!find_seed_language(seed, has_checksum, matched_indices, &language)) if (!find_seed_language(seed, has_checksum, matched_indices, &language))
{ {
@ -313,33 +319,33 @@ namespace crypto
for (unsigned int i=0; i < seed.size() / 3; i++) for (unsigned int i=0; i < seed.size() / 3; i++)
{ {
uint32_t val; uint32_t w[4];
uint32_t w1, w2, w3; w[1] = matched_indices[i*3];
w1 = matched_indices[i*3]; w[2] = matched_indices[i*3 + 1];
w2 = matched_indices[i*3 + 1]; w[3] = matched_indices[i*3 + 2];
w3 = matched_indices[i*3 + 2];
val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) + w[0]= w[1] + word_list_length * (((word_list_length - w[1]) + w[2]) % word_list_length) +
word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length); word_list_length * word_list_length * (((word_list_length - w[2]) + w[3]) % word_list_length);
if (!(val % word_list_length == w1)) if (!(w[0]% word_list_length == w[1]))
{ {
memwipe(w, sizeof(w));
MERROR("Invalid seed: mumble mumble"); MERROR("Invalid seed: mumble mumble");
return false; return false;
} }
dst.append((const char*)&val, 4); // copy 4 bytes to position dst.append((const char*)&w[0], 4); // copy 4 bytes to position
memwipe(w, sizeof(w));
} }
if (len > 0 && duplicate) if (len > 0 && duplicate)
{ {
const size_t expected = len * 3 / 32; const size_t expected = len * 3 / 32;
std::string wlist_copy = words;
if (seed.size() == expected/2) if (seed.size() == expected/2)
{ {
dst.append(dst); // if electrum 12-word seed, duplicate dst += ' '; // if electrum 12-word seed, duplicate
wlist_copy += ' '; dst += dst; // if electrum 12-word seed, duplicate
wlist_copy += words; dst.pop_back(); // trailing space
} }
} }
@ -353,10 +359,10 @@ namespace crypto
* \param language_name Language of the seed as found gets written here. * \param language_name Language of the seed as found gets written here.
* \return false if not a multiple of 3 words, or if word is not in the words list * \return false if not a multiple of 3 words, or if word is not in the words list
*/ */
bool words_to_bytes(std::string words, crypto::secret_key& dst, bool words_to_bytes(const epee::wipeable_string &words, crypto::secret_key& dst,
std::string &language_name) std::string &language_name)
{ {
std::string s; epee::wipeable_string s;
if (!words_to_bytes(words, s, sizeof(dst), true, language_name)) if (!words_to_bytes(words, s, sizeof(dst), true, language_name))
{ {
MERROR("Invalid seed: failed to convert words to bytes"); MERROR("Invalid seed: failed to convert words to bytes");
@ -378,7 +384,7 @@ namespace crypto
* \param language_name Seed language name * \param language_name Seed language name
* \return true if successful false if not. Unsuccessful if wrong key size. * \return true if successful false if not. Unsuccessful if wrong key size.
*/ */
bool bytes_to_words(const char *src, size_t len, std::string& words, bool bytes_to_words(const char *src, size_t len, epee::wipeable_string& words,
const std::string &language_name) const std::string &language_name)
{ {
@ -397,39 +403,38 @@ namespace crypto
} }
const std::vector<std::string> &word_list = language->get_word_list(); const std::vector<std::string> &word_list = language->get_word_list();
// To store the words for random access to add the checksum word later. // To store the words for random access to add the checksum word later.
std::vector<std::string> words_store; std::vector<epee::wipeable_string> words_store;
uint32_t word_list_length = word_list.size(); uint32_t word_list_length = word_list.size();
// 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 // 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626
for (unsigned int i=0; i < len/4; i++, words += ' ') for (unsigned int i=0; i < len/4; i++, words.push_back(' '))
{ {
uint32_t w1, w2, w3; uint32_t w[4];
uint32_t val;
memcpy(&val, src + (i * 4), 4); memcpy(&w[0], src + (i * 4), 4);
w1 = val % word_list_length; w[1] = w[0] % word_list_length;
w2 = ((val / word_list_length) + w1) % word_list_length; w[2] = ((w[0] / word_list_length) + w[1]) % word_list_length;
w3 = (((val / word_list_length) / word_list_length) + w2) % word_list_length; w[3] = (((w[0] / word_list_length) / word_list_length) + w[2]) % word_list_length;
words += word_list[w1]; words += word_list[w[1]];
words += ' '; words += ' ';
words += word_list[w2]; words += word_list[w[2]];
words += ' '; words += ' ';
words += word_list[w3]; words += word_list[w[3]];
words_store.push_back(word_list[w1]); words_store.push_back(word_list[w[1]]);
words_store.push_back(word_list[w2]); words_store.push_back(word_list[w[2]]);
words_store.push_back(word_list[w3]); words_store.push_back(word_list[w[3]]);
memwipe(w, sizeof(w));
} }
words.pop_back(); words += words_store[create_checksum_index(words_store, language->get_unique_prefix_length())];
words += (' ' + words_store[create_checksum_index(words_store, language->get_unique_prefix_length())]);
return true; return true;
} }
bool bytes_to_words(const crypto::secret_key& src, std::string& words, bool bytes_to_words(const crypto::secret_key& src, epee::wipeable_string& words,
const std::string &language_name) const std::string &language_name)
{ {
return bytes_to_words(src.data, sizeof(src), words, language_name); return bytes_to_words(src.data, sizeof(src), words, language_name);
@ -473,11 +478,10 @@ namespace crypto
* \param seed The seed to check (a space delimited concatenated word list) * \param seed The seed to check (a space delimited concatenated word list)
* \return true if the seed passed is a old style seed false if not. * \return true if the seed passed is a old style seed false if not.
*/ */
bool get_is_old_style_seed(std::string seed) bool get_is_old_style_seed(const epee::wipeable_string &seed)
{ {
std::vector<std::string> word_list; std::vector<epee::wipeable_string> word_list;
boost::algorithm::trim(seed); seed.split(word_list);
boost::split(word_list, seed, boost::is_any_of(" "), boost::token_compress_on);
return word_list.size() != (seed_length + 1); return word_list.size() != (seed_length + 1);
} }

View File

@ -44,6 +44,8 @@
#include <map> #include <map>
#include "crypto/crypto.h" // for declaration of crypto::secret_key #include "crypto/crypto.h" // for declaration of crypto::secret_key
namespace epee { class wipeable_string; }
/*! /*!
* \namespace crypto * \namespace crypto
* *
@ -70,7 +72,7 @@ namespace crypto
* \param language_name Language of the seed as found gets written here. * \param language_name Language of the seed as found gets written here.
* \return false if not a multiple of 3 words, or if word is not in the words list * \return false if not a multiple of 3 words, or if word is not in the words list
*/ */
bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, bool words_to_bytes(const epee::wipeable_string &words, epee::wipeable_string& dst, size_t len, bool duplicate,
std::string &language_name); std::string &language_name);
/*! /*!
* \brief Converts seed words to bytes (secret key). * \brief Converts seed words to bytes (secret key).
@ -79,7 +81,7 @@ namespace crypto
* \param language_name Language of the seed as found gets written here. * \param language_name Language of the seed as found gets written here.
* \return false if not a multiple of 3 words, or if word is not in the words list * \return false if not a multiple of 3 words, or if word is not in the words list
*/ */
bool words_to_bytes(std::string words, crypto::secret_key& dst, bool words_to_bytes(const epee::wipeable_string &words, crypto::secret_key& dst,
std::string &language_name); std::string &language_name);
/*! /*!
@ -90,7 +92,7 @@ namespace crypto
* \param language_name Seed language name * \param language_name Seed language name
* \return true if successful false if not. Unsuccessful if wrong key size. * \return true if successful false if not. Unsuccessful if wrong key size.
*/ */
bool bytes_to_words(const char *src, size_t len, std::string& words, bool bytes_to_words(const char *src, size_t len, epee::wipeable_string& words,
const std::string &language_name); const std::string &language_name);
/*! /*!
@ -100,7 +102,7 @@ namespace crypto
* \param language_name Seed language name * \param language_name Seed language name
* \return true if successful false if not. Unsuccessful if wrong key size. * \return true if successful false if not. Unsuccessful if wrong key size.
*/ */
bool bytes_to_words(const crypto::secret_key& src, std::string& words, bool bytes_to_words(const crypto::secret_key& src, epee::wipeable_string& words,
const std::string &language_name); const std::string &language_name);
/*! /*!
@ -115,7 +117,7 @@ namespace crypto
* \param seed The seed to check (a space delimited concatenated word list) * \param seed The seed to check (a space delimited concatenated word list)
* \return true if the seed passed is a old style seed false if not. * \return true if the seed passed is a old style seed false if not.
*/ */
bool get_is_old_style_seed(std::string seed); bool get_is_old_style_seed(const epee::wipeable_string &seed);
/*! /*!
* \brief Returns the name of a language in English * \brief Returns the name of a language in English

View File

@ -53,15 +53,20 @@ namespace Language
* \param count How many characters to return. * \param count How many characters to return.
* \return A string consisting of the first count characters in s. * \return A string consisting of the first count characters in s.
*/ */
inline std::string utf8prefix(const std::string &s, size_t count) template<typename T>
inline T utf8prefix(const T &s, size_t count)
{ {
std::string prefix = ""; T prefix = "";
const char *ptr = s.c_str(); size_t avail = s.size();
while (count-- && *ptr) const char *ptr = s.data();
while (count-- && avail--)
{ {
prefix += *ptr++; prefix += *ptr++;
while (((*ptr) & 0xc0) == 0x80) while (avail && ((*ptr) & 0xc0) == 0x80)
{
prefix += *ptr++; prefix += *ptr++;
--avail;
}
} }
return prefix; return prefix;
} }
@ -79,8 +84,8 @@ namespace Language
ALLOW_DUPLICATE_PREFIXES = 1<<1, ALLOW_DUPLICATE_PREFIXES = 1<<1,
}; };
const std::vector<std::string> word_list; /*!< A pointer to the array of words */ const std::vector<std::string> word_list; /*!< A pointer to the array of words */
std::unordered_map<std::string, uint32_t> word_map; /*!< hash table to find word's index */ std::unordered_map<epee::wipeable_string, uint32_t> word_map; /*!< hash table to find word's index */
std::unordered_map<std::string, uint32_t> trimmed_word_map; /*!< hash table to find word's trimmed index */ std::unordered_map<epee::wipeable_string, uint32_t> trimmed_word_map; /*!< hash table to find word's trimmed index */
std::string language_name; /*!< Name of language */ std::string language_name; /*!< Name of language */
std::string english_language_name; /*!< Name of language */ std::string english_language_name; /*!< Name of language */
uint32_t unique_prefix_length; /*!< Number of unique starting characters to trim the wordlist to when matching */ uint32_t unique_prefix_length; /*!< Number of unique starting characters to trim the wordlist to when matching */
@ -103,7 +108,7 @@ namespace Language
else else
throw std::runtime_error("Too short word in " + language_name + " word list: " + *it); throw std::runtime_error("Too short word in " + language_name + " word list: " + *it);
} }
std::string trimmed; epee::wipeable_string trimmed;
if (it->length() > unique_prefix_length) if (it->length() > unique_prefix_length)
{ {
trimmed = utf8prefix(*it, unique_prefix_length); trimmed = utf8prefix(*it, unique_prefix_length);
@ -115,9 +120,9 @@ namespace Language
if (trimmed_word_map.find(trimmed) != trimmed_word_map.end()) if (trimmed_word_map.find(trimmed) != trimmed_word_map.end())
{ {
if (flags & ALLOW_DUPLICATE_PREFIXES) if (flags & ALLOW_DUPLICATE_PREFIXES)
MWARNING("Duplicate prefix in " << language_name << " word list: " << trimmed); MWARNING("Duplicate prefix in " << language_name << " word list: " << std::string(trimmed.data(), trimmed.size()));
else else
throw std::runtime_error("Duplicate prefix in " + language_name + " word list: " + trimmed); throw std::runtime_error("Duplicate prefix in " + language_name + " word list: " + std::string(trimmed.data(), trimmed.size()));
} }
trimmed_word_map[trimmed] = ii; trimmed_word_map[trimmed] = ii;
} }
@ -145,7 +150,7 @@ namespace Language
* \brief Returns a pointer to the word map. * \brief Returns a pointer to the word map.
* \return A pointer to the word map. * \return A pointer to the word map.
*/ */
const std::unordered_map<std::string, uint32_t>& get_word_map() const const std::unordered_map<epee::wipeable_string, uint32_t>& get_word_map() const
{ {
return word_map; return word_map;
} }
@ -153,7 +158,7 @@ namespace Language
* \brief Returns a pointer to the trimmed word map. * \brief Returns a pointer to the trimmed word map.
* \return A pointer to the trimmed word map. * \return A pointer to the trimmed word map.
*/ */
const std::unordered_map<std::string, uint32_t>& get_trimmed_word_map() const const std::unordered_map<epee::wipeable_string, uint32_t>& get_trimmed_word_map() const
{ {
return trimmed_word_map; return trimmed_word_map;
} }

View File

@ -152,12 +152,31 @@ namespace
// //
// Note also that input for passwords is NOT translated, to remain compatible with any // Note also that input for passwords is NOT translated, to remain compatible with any
// passwords containing special characters that predate this switch to UTF-8 support. // passwords containing special characters that predate this switch to UTF-8 support.
static std::string cp850_to_utf8(const std::string &cp850_str) template<typename T>
static T cp850_to_utf8(const T &cp850_str)
{ {
boost::locale::generator gen; boost::locale::generator gen;
gen.locale_cache_enabled(true); gen.locale_cache_enabled(true);
std::locale loc = gen("en_US.CP850"); std::locale loc = gen("en_US.CP850");
return boost::locale::conv::to_utf<char>(cp850_str, loc); const boost::locale::conv::method_type how = boost::locale::conv::default_method;
T result;
const char *begin = cp850_str.data();
const char *end = begin + cp850_str.size();
result.reserve(end-begin);
typedef std::back_insert_iterator<T> inserter_type;
inserter_type inserter(result);
boost::locale::utf::code_point c;
while(begin!=end) {
c=boost::locale::utf::utf_traits<char>::template decode<char const *>(begin,end);
if(c==boost::locale::utf::illegal || c==boost::locale::utf::incomplete) {
if(how==boost::locale::conv::stop)
throw boost::locale::conv::conversion_error();
}
else {
boost::locale::utf::utf_traits<char>::template encode<inserter_type>(c,inserter);
}
}
return result;
} }
#endif #endif
@ -177,6 +196,28 @@ namespace
return epee::string_tools::trim(buf); return epee::string_tools::trim(buf);
} }
epee::wipeable_string input_secure_line(const std::string& prompt)
{
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
auto pwd_container = tools::password_container::prompt(false, prompt.c_str(), false);
if (!pwd_container)
{
MERROR("Failed to read secure line");
return "";
}
epee::wipeable_string buf = pwd_container->password();
#ifdef WIN32
buf = cp850_to_utf8(buf);
#endif
buf.trim();
return buf;
}
boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify) boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify)
{ {
#ifdef HAVE_READLINE #ifdef HAVE_READLINE
@ -595,7 +636,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
bool simple_wallet::print_seed(bool encrypted) bool simple_wallet::print_seed(bool encrypted)
{ {
bool success = false; bool success = false;
std::string seed; epee::wipeable_string seed;
bool ready, multisig; bool ready, multisig;
if (m_wallet->key_on_device()) if (m_wallet->key_on_device())
@ -2679,28 +2720,45 @@ bool simple_wallet::ask_wallet_create_if_needed()
* \brief Prints the seed with a nice message * \brief Prints the seed with a nice message
* \param seed seed to print * \param seed seed to print
*/ */
void simple_wallet::print_seed(std::string seed) void simple_wallet::print_seed(const epee::wipeable_string &seed)
{ {
success_msg_writer(true) << "\n" << tr("NOTE: the following 25 words can be used to recover access to your wallet. " success_msg_writer(true) << "\n" << tr("NOTE: the following 25 words can be used to recover access to your wallet. "
"Write them down and store them somewhere safe and secure. Please do not store them in " "Write them down and store them somewhere safe and secure. Please do not store them in "
"your email or on file storage services outside of your immediate control.\n"); "your email or on file storage services outside of your immediate control.\n");
boost::replace_nth(seed, " ", 15, "\n");
boost::replace_nth(seed, " ", 7, "\n");
// don't log // don't log
std::cout << seed << std::endl; int space_index = 0;
size_t len = seed.size();
for (const char *ptr = seed.data(); len--; ++ptr)
{
if (*ptr == ' ')
{
if (space_index == 15 || space_index == 7)
putchar('\n');
else
putchar(*ptr);
++space_index;
}
else
putchar(*ptr);
}
putchar('\n');
fflush(stdout);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
static bool might_be_partial_seed(std::string words) static bool might_be_partial_seed(const epee::wipeable_string &words)
{ {
std::vector<std::string> seed; std::vector<epee::wipeable_string> seed;
boost::algorithm::trim(words); words.split(seed);
boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on);
return seed.size() < 24; return seed.size() < 24;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool simple_wallet::init(const boost::program_options::variables_map& vm) bool simple_wallet::init(const boost::program_options::variables_map& vm)
{ {
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){
m_electrum_seed.wipe();
});
const bool testnet = tools::wallet2::has_testnet_option(vm); const bool testnet = tools::wallet2::has_testnet_option(vm);
const bool stagenet = tools::wallet2::has_stagenet_option(vm); const bool stagenet = tools::wallet2::has_stagenet_option(vm);
if (testnet && stagenet) if (testnet && stagenet)
@ -2710,7 +2768,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
} }
const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET;
std::string multisig_keys; epee::wipeable_string multisig_keys;
if (!handle_command_line(vm)) if (!handle_command_line(vm))
return false; return false;
@ -2752,8 +2810,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
{ {
if (m_restore_multisig_wallet) if (m_restore_multisig_wallet)
{ {
const char *prompt = "Specify multisig seed: "; const char *prompt = "Specify multisig seed";
m_electrum_seed = input_line(prompt); m_electrum_seed = input_secure_line(prompt);
if (std::cin.eof()) if (std::cin.eof())
return false; return false;
if (m_electrum_seed.empty()) if (m_electrum_seed.empty())
@ -2767,8 +2825,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
m_electrum_seed = ""; m_electrum_seed = "";
do do
{ {
const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed" : "Electrum seed continued";
std::string electrum_seed = input_line(prompt); epee::wipeable_string electrum_seed = input_secure_line(prompt);
if (std::cin.eof()) if (std::cin.eof())
return false; return false;
if (electrum_seed.empty()) if (electrum_seed.empty())
@ -2776,18 +2834,21 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\"");
return false; return false;
} }
m_electrum_seed += electrum_seed + " "; m_electrum_seed += electrum_seed;
m_electrum_seed += ' ';
} while (might_be_partial_seed(m_electrum_seed)); } while (might_be_partial_seed(m_electrum_seed));
} }
} }
if (m_restore_multisig_wallet) if (m_restore_multisig_wallet)
{ {
if (!epee::string_tools::parse_hexstr_to_binbuff(m_electrum_seed, multisig_keys)) const boost::optional<epee::wipeable_string> parsed = m_electrum_seed.parse_hexstr();
if (!parsed)
{ {
fail_msg_writer() << tr("Multisig seed failed verification"); fail_msg_writer() << tr("Multisig seed failed verification");
return false; return false;
} }
multisig_keys = *parsed;
} }
else else
{ {
@ -2809,7 +2870,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
crypto::secret_key key; crypto::secret_key key;
crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key);
sc_reduce32((unsigned char*)key.data); sc_reduce32((unsigned char*)key.data);
multisig_keys = m_wallet->decrypt(multisig_keys, key, true); multisig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true);
} }
else else
m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass);
@ -3478,7 +3539,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
} }
// convert rng value to electrum-style word list // convert rng value to electrum-style word list
std::string electrum_words; epee::wipeable_string electrum_words;
crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language); crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language);
@ -3586,7 +3647,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm,
const std::string &multisig_keys, const std::string &old_language) const epee::wipeable_string &multisig_keys, const std::string &old_language)
{ {
auto rc = tools::wallet2::make_new(vm, password_prompter); auto rc = tools::wallet2::make_new(vm, password_prompter);
m_wallet = std::move(rc.first); m_wallet = std::move(rc.first);
@ -3697,7 +3758,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
m_wallet->rewrite(m_wallet_file, password); m_wallet->rewrite(m_wallet_file, password);
// Display the seed // Display the seed
std::string seed; epee::wipeable_string seed;
m_wallet->get_seed(seed); m_wallet->get_seed(seed);
print_seed(seed); print_seed(seed);
} }

View File

@ -44,6 +44,7 @@
#include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/cryptonote_basic_impl.h"
#include "wallet/wallet2.h" #include "wallet/wallet2.h"
#include "console_handler.h" #include "console_handler.h"
#include "wipeable_string.h"
#include "common/i18n.h" #include "common/i18n.h"
#include "common/password.h" #include "common/password.h"
#include "crypto/crypto.h" // for definition of crypto::secret_key #include "crypto/crypto.h" // for definition of crypto::secret_key
@ -96,7 +97,7 @@ namespace cryptonote
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address,
const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey);
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm,
const std::string &multisig_keys, const std::string &old_language); const epee::wipeable_string &multisig_keys, const std::string &old_language);
boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name);
bool open_wallet(const boost::program_options::variables_map& vm); bool open_wallet(const boost::program_options::variables_map& vm);
bool close_wallet(); bool close_wallet();
@ -238,7 +239,7 @@ namespace cryptonote
* \brief Prints the seed with a nice message * \brief Prints the seed with a nice message
* \param seed seed to print * \param seed seed to print
*/ */
void print_seed(std::string seed); void print_seed(const epee::wipeable_string &seed);
/*! /*!
* \brief Gets the word seed language from the user. * \brief Gets the word seed language from the user.
@ -329,7 +330,7 @@ namespace cryptonote
std::string m_import_path; std::string m_import_path;
std::string m_subaddress_lookahead; std::string m_subaddress_lookahead;
std::string m_electrum_seed; // electrum-style seed parameter epee::wipeable_string m_electrum_seed; // electrum-style seed parameter
crypto::secret_key m_recovery_key; // recovery key (used as random for wallet gen) crypto::secret_key m_recovery_key; // recovery key (used as random for wallet gen)
bool m_restore_deterministic_wallet; // recover flag bool m_restore_deterministic_wallet; // recover flag

View File

@ -733,10 +733,10 @@ bool WalletImpl::close(bool store)
std::string WalletImpl::seed() const std::string WalletImpl::seed() const
{ {
std::string seed; epee::wipeable_string seed;
if (m_wallet) if (m_wallet)
m_wallet->get_seed(seed); m_wallet->get_seed(seed);
return seed; return std::string(seed.data(), seed.size()); // TODO
} }
std::string WalletImpl::getSeedLanguage() const std::string WalletImpl::getSeedLanguage() const

View File

@ -789,7 +789,7 @@ bool wallet2::is_deterministic() const
return keys_deterministic; return keys_deterministic;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase) const bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase) const
{ {
bool keys_deterministic = is_deterministic(); bool keys_deterministic = is_deterministic();
if (!keys_deterministic) if (!keys_deterministic)
@ -815,7 +815,7 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string
return true; return true;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase, bool raw) const bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase, bool raw) const
{ {
bool ready; bool ready;
uint32_t threshold, total; uint32_t threshold, total;
@ -838,7 +838,7 @@ bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &
crypto::secret_key skey; crypto::secret_key skey;
crypto::public_key pkey; crypto::public_key pkey;
const account_keys &keys = get_account().get_keys(); const account_keys &keys = get_account().get_keys();
std::string data; epee::wipeable_string data;
data.append((const char*)&threshold, sizeof(uint32_t)); data.append((const char*)&threshold, sizeof(uint32_t));
data.append((const char*)&total, sizeof(uint32_t)); data.append((const char*)&total, sizeof(uint32_t));
skey = keys.m_spend_secret_key; skey = keys.m_spend_secret_key;
@ -864,7 +864,7 @@ bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &
if (raw) if (raw)
{ {
seed = epee::string_tools::buff_to_hex_nodelimer(data); seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()});
} }
else else
{ {
@ -3161,7 +3161,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
* \param create_address_file Whether to create an address file * \param create_address_file Whether to create an address file
*/ */
void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password,
const std::string& multisig_data, bool create_address_file) const epee::wipeable_string& multisig_data, bool create_address_file)
{ {
clear(); clear();
prepare_file_names(wallet_); prepare_file_names(wallet_);
@ -10671,14 +10671,14 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
return n_outputs; return n_outputs;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const std::string wallet2::encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated) const
{ {
crypto::chacha_key key; crypto::chacha_key key;
crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds);
std::string ciphertext; std::string ciphertext;
crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>(); crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>();
ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); ciphertext.resize(len + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0));
crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); crypto::chacha20(plaintext, len, key, iv, &ciphertext[sizeof(iv)]);
memcpy(&ciphertext[0], &iv, sizeof(iv)); memcpy(&ciphertext[0], &iv, sizeof(iv));
if (authenticated) if (authenticated)
{ {
@ -10692,12 +10692,28 @@ std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_
return ciphertext; return ciphertext;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt(const epee::span<char> &plaintext, const crypto::secret_key &skey, bool authenticated) const
{
return encrypt(plaintext.data(), plaintext.size(), skey, authenticated);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const
{
return encrypt(plaintext.data(), plaintext.size(), skey, authenticated);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt(const epee::wipeable_string &plaintext, const crypto::secret_key &skey, bool authenticated) const
{
return encrypt(plaintext.data(), plaintext.size(), skey, authenticated);
}
//----------------------------------------------------------------------------------------------------
std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const
{ {
return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated); return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const template<typename T>
T wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const
{ {
const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0); const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0);
THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size, THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size,
@ -10706,8 +10722,6 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret
crypto::chacha_key key; crypto::chacha_key key;
crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds); crypto::generate_chacha_key(&skey, sizeof(skey), key, m_kdf_rounds);
const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0];
std::string plaintext;
plaintext.resize(ciphertext.size() - prefix_size);
if (authenticated) if (authenticated)
{ {
crypto::hash hash; crypto::hash hash;
@ -10718,10 +10732,14 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret
THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature),
error::wallet_internal_error, "Failed to authenticate ciphertext"); error::wallet_internal_error, "Failed to authenticate ciphertext");
} }
crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); std::unique_ptr<char[]> buffer{new char[ciphertext.size() - prefix_size]};
return plaintext; auto wiper = epee::misc_utils::create_scope_leave_handler([&]() { memwipe(buffer.get(), ciphertext.size() - prefix_size); });
crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, buffer.get());
return T(buffer.get(), ciphertext.size() - prefix_size);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
template epee::wipeable_string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const;
//----------------------------------------------------------------------------------------------------
std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const
{ {
return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated);

View File

@ -495,7 +495,7 @@ namespace tools
* \param create_address_file Whether to create an address file * \param create_address_file Whether to create an address file
*/ */
void generate(const std::string& wallet_, const epee::wipeable_string& password, void generate(const std::string& wallet_, const epee::wipeable_string& password,
const std::string& multisig_data, bool create_address_file = false); const epee::wipeable_string& multisig_data, bool create_address_file = false);
/*! /*!
* \brief Generates a wallet or restores one. * \brief Generates a wallet or restores one.
@ -637,7 +637,7 @@ namespace tools
* \brief Checks if deterministic wallet * \brief Checks if deterministic wallet
*/ */
bool is_deterministic() const; bool is_deterministic() const;
bool get_seed(std::string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const;
/*! /*!
* \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned.
@ -691,7 +691,7 @@ namespace tools
bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
bool has_multisig_partial_key_images() const; bool has_multisig_partial_key_images() const;
bool has_unknown_key_images() const; bool has_unknown_key_images() const;
bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const;
bool key_on_device() const { return m_key_on_device; } bool key_on_device() const { return m_key_on_device; }
// locked & unlocked balance of given or current subaddress account // locked & unlocked balance of given or current subaddress account
@ -1054,9 +1054,12 @@ namespace tools
void update_pool_state(bool refreshed = false); void update_pool_state(bool refreshed = false);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes); void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt(const epee::span<char> &span, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt(const epee::wipeable_string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const;
std::string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; template<typename T=std::string> T decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const;
std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const; std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const;
std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const; std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const;

View File

@ -1574,11 +1574,13 @@ namespace tools
if (req.key_type.compare("mnemonic") == 0) if (req.key_type.compare("mnemonic") == 0)
{ {
if (!m_wallet->get_seed(res.key)) epee::wipeable_string seed;
if (!m_wallet->get_seed(seed))
{ {
er.message = "The wallet is non-deterministic. Cannot display seed."; er.message = "The wallet is non-deterministic. Cannot display seed.";
return false; return false;
} }
res.key = std::string(seed.data(), seed.size()); // send to the network, then wipe RAM :D
} }
else if(req.key_type.compare("view_key") == 0) else if(req.key_type.compare("view_key") == 0)
{ {

View File

@ -72,7 +72,8 @@ set(unit_tests_sources
ringct.cpp ringct.cpp
output_selection.cpp output_selection.cpp
vercmp.cpp vercmp.cpp
ringdb.cpp) ringdb.cpp
wipeable_string.cpp)
set(unit_tests_headers set(unit_tests_headers
unit_tests_utils.h) unit_tests_utils.h)

View File

@ -27,6 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "wipeable_string.h"
#include "mnemonics/language_base.h"
#include "mnemonics/electrum-words.h" #include "mnemonics/electrum-words.h"
#include "crypto/crypto.h" #include "crypto/crypto.h"
#include <stdlib.h> #include <stdlib.h>
@ -74,14 +76,16 @@ namespace
void test_language(const Language::Base &language) void test_language(const Language::Base &language)
{ {
const std::vector<std::string> &word_list = language.get_word_list(); const std::vector<std::string> &word_list = language.get_word_list();
std::string seed = "", return_seed = ""; epee::wipeable_string w_seed = "", w_return_seed = "";
std::string seed, return_seed;
// Generate a random seed without checksum // Generate a random seed without checksum
crypto::secret_key randkey; crypto::secret_key randkey;
for (size_t ii = 0; ii < sizeof(randkey); ++ii) for (size_t ii = 0; ii < sizeof(randkey); ++ii)
{ {
randkey.data[ii] = rand(); randkey.data[ii] = rand();
} }
crypto::ElectrumWords::bytes_to_words(randkey, seed, language.get_language_name()); crypto::ElectrumWords::bytes_to_words(randkey, w_seed, language.get_language_name());
seed = std::string(w_seed.data(), w_seed.size());
// remove the checksum word // remove the checksum word
const char *space = strrchr(seed.c_str(), ' '); const char *space = strrchr(seed.c_str(), ' ');
ASSERT_TRUE(space != NULL); ASSERT_TRUE(space != NULL);
@ -103,7 +107,8 @@ namespace
ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str()); ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str());
// Convert the secret key back to seed // Convert the secret key back to seed
crypto::ElectrumWords::bytes_to_words(key, return_seed, language.get_language_name()); crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name());
return_seed = std::string(w_return_seed.data(), w_return_seed.size());
ASSERT_EQ(true, res); ASSERT_EQ(true, res);
std::cout << "Returned seed:\n"; std::cout << "Returned seed:\n";
std::cout << return_seed << std::endl; std::cout << return_seed << std::endl;
@ -126,8 +131,9 @@ namespace
std::cout << "Detected language: " << language_name << std::endl; std::cout << "Detected language: " << language_name << std::endl;
ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str()); ASSERT_STREQ(language.get_language_name().c_str(), language_name.c_str());
return_seed = ""; w_return_seed = "";
crypto::ElectrumWords::bytes_to_words(key, return_seed, language.get_language_name()); crypto::ElectrumWords::bytes_to_words(key, w_return_seed, language.get_language_name());
return_seed = std::string(w_return_seed.data(), w_return_seed.size());
ASSERT_EQ(true, res); ASSERT_EQ(true, res);
std::cout << "Returned seed:\n"; std::cout << "Returned seed:\n";
std::cout << return_seed << std::endl; std::cout << return_seed << std::endl;
@ -202,3 +208,17 @@ TEST(mnemonics, language_detection_with_bad_checksum)
ASSERT_EQ(true, res); ASSERT_EQ(true, res);
ASSERT_STREQ(language_name.c_str(), "Português"); ASSERT_STREQ(language_name.c_str(), "Português");
} }
TEST(mnemonics, utf8prefix)
{
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 0) == "");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 1) == "f");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 2) == "fo");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 3) == "foo");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("foo"), 4) == "foo");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 0) == "");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 1) == "æ");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 2) == "æo");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 3) == "æon");
ASSERT_TRUE(Language::utf8prefix(epee::wipeable_string("æon"), 4) == "æon");
}

View File

@ -0,0 +1,204 @@
// Copyright (c) 2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/optional/optional.hpp>
#include <string.h>
#include "gtest/gtest.h"
#include "misc_log_ex.h"
#include "wipeable_string.h"
TEST(wipeable_string, ctor)
{
epee::wipeable_string s0;
ASSERT_EQ(s0.size(), 0);
epee::wipeable_string s1(std::string("foo"));
ASSERT_EQ(s1.size(), 3);
ASSERT_TRUE(!memcmp(s1.data(), "foo", s1.size()));
epee::wipeable_string s2(std::string("bar"));
ASSERT_EQ(s2.size(), 3);
ASSERT_TRUE(!memcmp(s2.data(), "bar", s2.size()));
epee::wipeable_string s3(std::string("quux"));
ASSERT_EQ(s3.size(), 4);
ASSERT_TRUE(!memcmp(s3.data(), "quux", s3.size()));
}
TEST(wipeable_string, wipe)
{
epee::wipeable_string s0(std::string("foo"));
ASSERT_EQ(s0.size(), 3);
s0.wipe();
ASSERT_EQ(s0.size(), 3);
ASSERT_TRUE(!memcmp(s0.data(), "\0\0\0", 3));
}
TEST(wipeable_string, clear)
{
epee::wipeable_string s0(std::string("foo"));
ASSERT_EQ(s0.size(), 3);
s0.clear();
ASSERT_EQ(s0.size(), 0);
}
TEST(wipeable_string, push_back)
{
epee::wipeable_string s0(std::string("fo"));
ASSERT_EQ(s0.size(), 2);
s0.push_back('o');
ASSERT_EQ(s0.size(), 3);
ASSERT_TRUE(!memcmp(s0.data(), "foo", s0.size()));
}
TEST(wipeable_string, append_char)
{
epee::wipeable_string s0(std::string("fo"));
ASSERT_EQ(s0.size(), 2);
s0 += 'o';
ASSERT_EQ(s0.size(), 3);
ASSERT_TRUE(!memcmp(s0.data(), "foo", s0.size()));
}
TEST(wipeable_string, append_string)
{
epee::wipeable_string s0(std::string("foo"));
ASSERT_EQ(s0.size(), 3);
s0 += "bar";
ASSERT_EQ(s0.size(), 6);
ASSERT_TRUE(!memcmp(s0.data(), "foobar", s0.size()));
}
TEST(wipeable_string, empty)
{
epee::wipeable_string s0;
ASSERT_TRUE(s0.empty());
s0.push_back(' ');
ASSERT_FALSE(s0.empty());
ASSERT_EQ(s0.pop_back(), ' ');
ASSERT_TRUE(s0.empty());
}
TEST(wipeable_string, pop_back)
{
epee::wipeable_string s = "test";
ASSERT_EQ(s.size(), 4);
ASSERT_EQ(s.pop_back(), 't');
ASSERT_EQ(s.size(), 3);
ASSERT_TRUE(!memcmp(s.data(), "tes", s.size()));
}
TEST(wipeable_string, equal)
{
epee::wipeable_string s0 = "foo";
epee::wipeable_string s1 = "bar";
epee::wipeable_string s0_2 = "foo";
ASSERT_TRUE(s0 == s0);
ASSERT_TRUE(s0 == s0_2);
ASSERT_TRUE(s1 == s1);
ASSERT_FALSE(s1 == s0);
ASSERT_FALSE(s1 == s0_2);
}
TEST(wipeable_string, not_equal)
{
epee::wipeable_string s0 = "foo";
epee::wipeable_string s1 = "bar";
epee::wipeable_string s0_2 = "foo";
ASSERT_FALSE(s0 != s0);
ASSERT_FALSE(s0 != s0_2);
ASSERT_FALSE(s1 != s1);
ASSERT_TRUE(s1 != s0);
ASSERT_TRUE(s1 != s0_2);
}
static epee::wipeable_string trimmed(const char *s)
{
epee::wipeable_string str(s);
str.trim();
return str;
}
TEST(wipeable_string, trim)
{
ASSERT_TRUE(trimmed("") == "");
ASSERT_TRUE(trimmed(" ") == "");
ASSERT_TRUE(trimmed(" ") == "");
ASSERT_TRUE(trimmed("a") == "a");
ASSERT_TRUE(trimmed(" a") == "a");
ASSERT_TRUE(trimmed(" a") == "a");
ASSERT_TRUE(trimmed("a ") == "a");
ASSERT_TRUE(trimmed("a ") == "a");
ASSERT_TRUE(trimmed(" a ") == "a");
ASSERT_TRUE(trimmed(" a ") == "a");
ASSERT_TRUE(trimmed(" ab ") == "ab");
ASSERT_TRUE(trimmed(" a b ") == "a b");
ASSERT_TRUE(trimmed(" a b ") == "a b");
}
static bool check_split(const char *s, const std::vector<epee::wipeable_string> &v)
{
epee::wipeable_string str(s);
std::vector<epee::wipeable_string> fields;
str.split(fields);
return v == fields;
}
TEST(wipeable_string, split)
{
ASSERT_TRUE(check_split("", {}));
ASSERT_TRUE(check_split("foo", {"foo"}));
ASSERT_TRUE(check_split(" foo ", {"foo"}));
ASSERT_TRUE(check_split("foo bar", {"foo", "bar"}));
ASSERT_TRUE(check_split("foo bar", {"foo", "bar"}));
ASSERT_TRUE(check_split("foo bar baz", {"foo", "bar", "baz"}));
ASSERT_TRUE(check_split(" foo bar baz ", {"foo", "bar", "baz"}));
ASSERT_TRUE(check_split(" foo bar baz", {"foo", "bar", "baz"}));
ASSERT_TRUE(check_split("foo bar baz ", {"foo", "bar", "baz"}));
}
TEST(wipeable_string, parse_hexstr)
{
boost::optional<epee::wipeable_string> s;
ASSERT_EQ(boost::none, epee::wipeable_string("x").parse_hexstr());
ASSERT_EQ(boost::none, epee::wipeable_string("x0000000000000000").parse_hexstr());
ASSERT_EQ(boost::none, epee::wipeable_string("0000000000000000x").parse_hexstr());
ASSERT_EQ(boost::none, epee::wipeable_string("0").parse_hexstr());
ASSERT_EQ(boost::none, epee::wipeable_string("000").parse_hexstr());
ASSERT_TRUE((s = epee::wipeable_string("").parse_hexstr()));
ASSERT_EQ(*s, "");
ASSERT_TRUE((s = epee::wipeable_string("00").parse_hexstr()));
ASSERT_EQ(*s, epee::wipeable_string("", 1));
ASSERT_TRUE((s = epee::wipeable_string("41").parse_hexstr()));
ASSERT_EQ(*s, epee::wipeable_string("A"));
ASSERT_TRUE((s = epee::wipeable_string("414243").parse_hexstr()));
ASSERT_EQ(*s, epee::wipeable_string("ABC"));
}