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 {
public:
virtual ~Console() {}
virtual ~Console() = default;
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 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 <boost/algorithm/string/trim.hpp>
#include "DontEchoStdinToStdoutRAII.h"
using std::ostream;
using std::istream;
@ -91,8 +92,24 @@ bool IOStreamConsole::askYesNo(const string &question, bool /*defaultValue*/) {
return _askForChoice("Your choice [y/n]: ", _parseYesNo());
}
void IOStreamConsole::print(const std::string &output) {
void IOStreamConsole::print(const string &output) {
_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;
bool askYesNo(const std::string &question, bool defaultValue) override;
void print(const std::string &output) override;
std::string askPassword(const std::string &question) override;
private:
template<typename Return>
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");
}
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;
bool askYesNo(const std::string &question, bool defaultValue) override;
void print(const std::string &output) override;
std::string askPassword(const std::string &question) override;
private:
std::shared_ptr<Console> _baseConsole;

View File

@ -124,34 +124,38 @@ namespace cryfs {
return true;
}
string Cli::_askPasswordForExistingFilesystem() {
string password = _askPasswordFromStdin("Password: ");
while (!_checkPassword(password)) {
password = _askPasswordFromStdin("Password: ");
}
return password;
function<string()> Cli::_askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console) {
return [console] () {
string password = console->askPassword("Password: ");
while (!_checkPassword(password)) {
password = console->askPassword("Password: ");
}
return password;
};
};
string Cli::_askPasswordForNewFilesystem() {
string password;
bool again = false;
do {
password = _askPasswordFromStdin("Password: ");
if (!_checkPassword(password)) {
again = true;
continue;
}
if (!_confirmPassword(password)) {
again = true;
continue;
}
again = false;
} while(again);
return password;
function<string()> Cli::_askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console) {
return [console] () {
string password;
bool again = false;
do {
password = console->askPassword("Password: ");
if (!_checkPassword(password)) {
again = true;
continue;
}
if (!_confirmPassword(console.get(), password)) {
again = true;
continue;
}
again = false;
} while (again);
return password;
};
}
bool Cli::_confirmPassword(const string &password) {
string confirmPassword = _askPasswordFromStdin("Confirm Password: ");
bool Cli::_confirmPassword(cpputils::Console* console, const string &password) {
string confirmPassword = console->askPassword("Confirm Password: ");
if (password != confirmPassword) {
std::cout << "Passwords don't match" << std::endl;
return false;
@ -159,29 +163,15 @@ namespace cryfs {
return true;
}
string Cli::_askPasswordNoninteractive() {
function<string()> Cli::_askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console) {
//TODO Test
string password = _askPasswordFromStdin("Password: ");
if (!_checkPassword(password)) {
throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
}
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;
return [console] () {
string password = console->askPassword("Password: ");
if (!_checkPassword(password)) {
throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
}
return password;
};
}
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) {
if (_noninteractive) {
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
&Cli::_askPasswordNoninteractive,
&Cli::_askPasswordNoninteractive,
Cli::_askPasswordNoninteractive(_console),
Cli::_askPasswordNoninteractive(_console),
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
} else {
return CryConfigLoader(_console, _keyGenerator, std::move(localStateDir), _scryptSettings,
&Cli::_askPasswordForExistingFilesystem,
&Cli::_askPasswordForNewFilesystem,
Cli::_askPasswordForExistingFilesystem(_console),
Cli::_askPasswordForNewFilesystem(_console),
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);
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);
static std::string _askPasswordForExistingFilesystem();
static std::string _askPasswordForNewFilesystem();
static std::string _askPasswordNoninteractive();
static std::string _askPasswordFromStdin(const std::string &prompt);
static bool _confirmPassword(const std::string &password);
static std::function<std::string()> _askPasswordForExistingFilesystem(std::shared_ptr<cpputils::Console> console);
static std::function<std::string()> _askPasswordForNewFilesystem(std::shared_ptr<cpputils::Console> console);
static std::function<std::string()> _askPasswordNoninteractive(std::shared_ptr<cpputils::Console> console);
static bool _confirmPassword(cpputils::Console* console, const std::string &password);
static bool _checkPassword(const std::string &password);
void _showVersion(cpputils::unique_ref<cpputils::HttpClient> httpClient);
void _initLogfile(const program_options::ProgramOptions &options);

View File

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

View File

@ -23,6 +23,11 @@ public:
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) {
_console.print(output);
}
@ -63,6 +68,10 @@ public:
return _console.askYesNo(question);
}
std::future<std::string> askPassword(const std::string &question) {
return _console.askPassword(question);
}
void print(const std::string &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);
}
auto &keyGenerator = cpputils::Random::PseudoRandom();
// Write 2x 'pass\n' to stdin so Cryfs can read it as password (+ password confirmation prompt)
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
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("Password: "))).WillByDefault(testing::Return("pass"));
ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
// Run Cryfs
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_METHOD2(ask, unsigned int(const std::string&, const std::vector<std::string>&));
MOCK_METHOD2(askYesNo, bool(const std::string&, bool));
MOCK_METHOD1(askPassword, std::string(const std::string&));
};
ACTION_P(ChooseCipher, cipherName) {