Portable way to ask for password

This commit is contained in:
Sebastian Messmer 2018-09-03 16:51:59 -07:00
parent 4778c28898
commit fafbbb8e3a
12 changed files with 105 additions and 60 deletions

View File

@ -13,10 +13,11 @@ namespace cpputils {
class Console { class Console {
public: public:
virtual ~Console() {} virtual ~Console() = default;
virtual unsigned int ask(const std::string &question, const std::vector<std::string> &options) = 0; virtual unsigned int ask(const std::string &question, const std::vector<std::string> &options) = 0;
virtual bool askYesNo(const std::string &question, bool defaultValue) = 0; // NoninteractiveConsole will just return the default value without asking the user. virtual bool askYesNo(const std::string &question, bool defaultValue) = 0; // NoninteractiveConsole will just return the default value without asking the user.
virtual void print(const std::string &output) = 0; virtual void print(const std::string &output) = 0;
virtual std::string askPassword(const std::string &question) = 0;
}; };
} }

View File

@ -1,5 +1,6 @@
#include "IOStreamConsole.h" #include "IOStreamConsole.h"
#include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/trim.hpp>
#include "DontEchoStdinToStdoutRAII.h"
using std::ostream; using std::ostream;
using std::istream; using std::istream;
@ -91,8 +92,24 @@ bool IOStreamConsole::askYesNo(const string &question, bool /*defaultValue*/) {
return _askForChoice("Your choice [y/n]: ", _parseYesNo()); return _askForChoice("Your choice [y/n]: ", _parseYesNo());
} }
void IOStreamConsole::print(const std::string &output) { void IOStreamConsole::print(const string &output) {
_output << output << std::flush; _output << output << std::flush;
} }
string IOStreamConsole::askPassword(const string &question) {
DontEchoStdinToStdoutRAII _stdin_input_is_hidden_as_long_as_this_is_in_scope;
_output << question << std::flush;
string result;
std::getline(_input, result);
_output << std::endl;
//Remove trailing newline
if (result[result.size()-1] == '\n') {
result.resize(result.size()-1);
}
return result;
}
} }

View File

@ -13,6 +13,7 @@ namespace cpputils {
unsigned int ask(const std::string &question, const std::vector<std::string> &options) override; unsigned int ask(const std::string &question, const std::vector<std::string> &options) override;
bool askYesNo(const std::string &question, bool defaultValue) override; bool askYesNo(const std::string &question, bool defaultValue) override;
void print(const std::string &output) override; void print(const std::string &output) override;
std::string askPassword(const std::string &question) override;
private: private:
template<typename Return> template<typename Return>
Return _askForChoice(const std::string &question, std::function<boost::optional<Return> (const std::string&)> parse); Return _askForChoice(const std::string &question, std::function<boost::optional<Return> (const std::string&)> parse);

View File

@ -21,4 +21,8 @@ unsigned int NoninteractiveConsole::ask(const string &/*question*/, const vector
throw std::logic_error("Tried to ask a multiple choice question in noninteractive mode"); throw std::logic_error("Tried to ask a multiple choice question in noninteractive mode");
} }
string NoninteractiveConsole::askPassword(const string &question) {
return _baseConsole->askPassword(question);
}
} }

View File

@ -14,6 +14,7 @@ namespace cpputils {
unsigned int ask(const std::string &question, const std::vector<std::string> &options) override; unsigned int ask(const std::string &question, const std::vector<std::string> &options) override;
bool askYesNo(const std::string &question, bool defaultValue) override; bool askYesNo(const std::string &question, bool defaultValue) override;
void print(const std::string &output) override; void print(const std::string &output) override;
std::string askPassword(const std::string &question) override;
private: private:
std::shared_ptr<Console> _baseConsole; std::shared_ptr<Console> _baseConsole;

View File

@ -124,34 +124,38 @@ namespace cryfs {
return true; return true;
} }
string Cli::_askPasswordForExistingFilesystem() { function<string()> Cli::_askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console) {
string password = _askPasswordFromStdin("Password: "); return [console] () {
string password = console->askPassword("Password: ");
while (!_checkPassword(password)) { while (!_checkPassword(password)) {
password = _askPasswordFromStdin("Password: "); password = console->askPassword("Password: ");
} }
return password; return password;
}; };
};
string Cli::_askPasswordForNewFilesystem() { function<string()> Cli::_askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console) {
return [console] () {
string password; string password;
bool again = false; bool again = false;
do { do {
password = _askPasswordFromStdin("Password: "); password = console->askPassword("Password: ");
if (!_checkPassword(password)) { if (!_checkPassword(password)) {
again = true; again = true;
continue; continue;
} }
if (!_confirmPassword(password)) { if (!_confirmPassword(console.get(), password)) {
again = true; again = true;
continue; continue;
} }
again = false; again = false;
} while(again); } while (again);
return password; return password;
};
} }
bool Cli::_confirmPassword(const string &password) { bool Cli::_confirmPassword(cpputils::Console* console, const string &password) {
string confirmPassword = _askPasswordFromStdin("Confirm Password: "); string confirmPassword = console->askPassword("Confirm Password: ");
if (password != confirmPassword) { if (password != confirmPassword) {
std::cout << "Passwords don't match" << std::endl; std::cout << "Passwords don't match" << std::endl;
return false; return false;
@ -159,29 +163,15 @@ namespace cryfs {
return true; return true;
} }
string Cli::_askPasswordNoninteractive() { function<string()> Cli::_askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console) {
//TODO Test //TODO Test
string password = _askPasswordFromStdin("Password: "); return [console] () {
string password = console->askPassword("Password: ");
if (!_checkPassword(password)) { if (!_checkPassword(password)) {
throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword); throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
} }
return password; return password;
} };
string Cli::_askPasswordFromStdin(const string &prompt) {
DontEchoStdinToStdoutRAII _stdin_input_is_hidden_as_long_as_this_is_in_scope;
std::cout << prompt << std::flush;
string result;
std::getline(cin, result);
std::cout << std::endl;
//Remove trailing newline
if (result[result.size()-1] == '\n') {
result.resize(result.size()-1);
}
return result;
} }
bf::path Cli::_determineConfigFile(const ProgramOptions &options) { bf::path Cli::_determineConfigFile(const ProgramOptions &options) {
@ -218,13 +208,13 @@ namespace cryfs {
optional<CryConfigLoader::ConfigLoadResult> Cli::_loadOrCreateConfigFile(bf::path configFilePath, LocalStateDir localStateDir, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem) { optional<CryConfigLoader::ConfigLoadResult> Cli::_loadOrCreateConfigFile(bf::path configFilePath, LocalStateDir localStateDir, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem) {
if (_noninteractive) { if (_noninteractive) {
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings, return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
&Cli::_askPasswordNoninteractive, Cli::_askPasswordNoninteractive(_console),
&Cli::_askPasswordNoninteractive, Cli::_askPasswordNoninteractive(_console),
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem); cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
} else { } else {
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings, return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
&Cli::_askPasswordForExistingFilesystem, Cli::_askPasswordForExistingFilesystem(_console),
&Cli::_askPasswordForNewFilesystem, Cli::_askPasswordForNewFilesystem(_console),
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem); cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
} }
} }

View File

@ -27,11 +27,10 @@ namespace cryfs {
void _checkConfigIntegrity(const boost::filesystem::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem); void _checkConfigIntegrity(const boost::filesystem::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem);
boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem); boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem);
boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options); boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options);
static std::string _askPasswordForExistingFilesystem(); static std::function<std::string()> _askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console);
static std::string _askPasswordForNewFilesystem(); static std::function<std::string()> _askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console);
static std::string _askPasswordNoninteractive(); static std::function<std::string()> _askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console);
static std::string _askPasswordFromStdin(const std::string &prompt); static bool _confirmPassword(cpputils::Console* console, const std::string &password);
static bool _confirmPassword(const std::string &password);
static bool _checkPassword(const std::string &password); static bool _checkPassword(const std::string &password);
void _showVersion(cpputils::unique_ref<cpputils::HttpClient> httpClient); void _showVersion(cpputils::unique_ref<cpputils::HttpClient> httpClient);
void _initLogfile(const program_options::ProgramOptions &options); void _initLogfile(const program_options::ProgramOptions &options);

View File

@ -27,6 +27,7 @@ set(SOURCES
io/ConsoleTest_AskYesNo.cpp io/ConsoleTest_AskYesNo.cpp
io/ConsoleTest_Print.cpp io/ConsoleTest_Print.cpp
io/ConsoleTest_Ask.cpp io/ConsoleTest_Ask.cpp
io/ConsoleTest_AskPassword.cpp
random/RandomIncludeTest.cpp random/RandomIncludeTest.cpp
lock/LockPoolIncludeTest.cpp lock/LockPoolIncludeTest.cpp
lock/ConditionBarrierIncludeTest.cpp lock/ConditionBarrierIncludeTest.cpp

View File

@ -23,6 +23,11 @@ public:
return _console.askYesNo(question, true); return _console.askYesNo(question, true);
}); });
} }
std::future<std::string> askPassword(const std::string &question) {
return std::async(std::launch::async, [this, question]() {
return _console.askPassword(question);
});
}
void print(const std::string &output) { void print(const std::string &output) {
_console.print(output); _console.print(output);
} }
@ -63,6 +68,10 @@ public:
return _console.askYesNo(question); return _console.askYesNo(question);
} }
std::future<std::string> askPassword(const std::string &question) {
return _console.askPassword(question);
}
void print(const std::string &output) { void print(const std::string &output) {
_console.print(output); _console.print(output);
} }

View File

@ -0,0 +1,22 @@
#include "ConsoleTest.h"
using std::stringstream;
using std::string;
using std::istream;
using std::ostream;
class ConsoleTest_AskPassword: public ConsoleTest {};
TEST_F(ConsoleTest_AskPassword, InputSomePassword) {
auto chosen = askPassword("Please enter my password:");
EXPECT_OUTPUT_LINE("Please enter my password", ':');
sendInputLine("this is the password");
EXPECT_EQ("this is the password", chosen.get());
}
TEST_F(ConsoleTest_AskPassword, InputEmptyPassword) {
auto chosen = askPassword("Please enter my password:");
EXPECT_OUTPUT_LINE("Please enter my password", ':');
sendInputLine("");
EXPECT_EQ("", chosen.get());
}

View File

@ -42,9 +42,8 @@ public:
_args.push_back(arg); _args.push_back(arg);
} }
auto &keyGenerator = cpputils::Random::PseudoRandom(); auto &keyGenerator = cpputils::Random::PseudoRandom();
// Write 2x 'pass\n' to stdin so Cryfs can read it as password (+ password confirmation prompt) ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass"));
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p'); ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
// Run Cryfs // Run Cryfs
return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient()); return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient());
} }

View File

@ -10,6 +10,7 @@ public:
MOCK_METHOD1(print, void(const std::string&)); MOCK_METHOD1(print, void(const std::string&));
MOCK_METHOD2(ask, unsigned int(const std::string&, const std::vector<std::string>&)); MOCK_METHOD2(ask, unsigned int(const std::string&, const std::vector<std::string>&));
MOCK_METHOD2(askYesNo, bool(const std::string&, bool)); MOCK_METHOD2(askYesNo, bool(const std::string&, bool));
MOCK_METHOD1(askPassword, std::string(const std::string&));
}; };
ACTION_P(ChooseCipher, cipherName) { ACTION_P(ChooseCipher, cipherName) {