Add --allow-replaced-filesystem option to disable filesystem id checks

This commit is contained in:
Sebastian Messmer 2018-02-03 08:33:59 -08:00
parent 400a5b5397
commit 652b505eef
13 changed files with 45 additions and 31 deletions

View File

@ -169,6 +169,12 @@ Allow upgrading the file system if it was created with an old CryFS version. Aft
.
.
.TP
\fB\-\-allow-replaced-filesystem\fI
.
By default, CryFS remembers file systems it has seen in this base directory and checks that it didn't get replaced by an attacker with an entirely different file system since the last time it was loaded. However, if you do want to replace the file system with an entirely new one, you can pass in this option to disable the check.
.
.
.TP
\fB\-\-logfile\fR \fIfile\fR
.
Write status information to \fIfile\fR. If no logfile is given, CryFS will

View File

@ -192,9 +192,9 @@ namespace cryfs {
return *configFile;
}
void Cli::_checkConfigIntegrity(const bf::path& basedir, const CryConfigFile& config) {
void Cli::_checkConfigIntegrity(const bf::path& basedir, const CryConfigFile& config, bool allowReplacedFilesystem) {
auto basedirMetadata = BasedirMetadata::load();
if (!basedirMetadata.filesystemIdForBasedirIsCorrect(basedir, config.config()->FilesystemId())) {
if (!allowReplacedFilesystem && !basedirMetadata.filesystemIdForBasedirIsCorrect(basedir, config.config()->FilesystemId())) {
if (!_console->askYesNo("The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This can be genuine if you replaced the filesystem with a different one. If you didn't do that, it is possible that an attacker did. Do you want to continue loading the file system?", false)) {
throw CryfsException(
"The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir.", ErrorCode::FilesystemIdChanged);
@ -207,25 +207,25 @@ namespace cryfs {
CryConfigLoader::ConfigLoadResult Cli::_loadOrCreateConfig(const ProgramOptions &options) {
auto configFile = _determineConfigFile(options);
auto config = _loadOrCreateConfigFile(std::move(configFile), options.cipher(), options.blocksizeBytes(), options.allowFilesystemUpgrade(), options.missingBlockIsIntegrityViolation());
auto config = _loadOrCreateConfigFile(std::move(configFile), options.cipher(), options.blocksizeBytes(), options.allowFilesystemUpgrade(), options.missingBlockIsIntegrityViolation(), options.allowReplacedFilesystem());
if (config == none) {
throw CryfsException("Could not load config file. Did you enter the correct password?", ErrorCode::WrongPassword);
}
_checkConfigIntegrity(options.baseDir(), config->configFile);
_checkConfigIntegrity(options.baseDir(), config->configFile, options.allowReplacedFilesystem());
return std::move(*config);
}
optional<CryConfigLoader::ConfigLoadResult> Cli::_loadOrCreateConfigFile(bf::path configFilePath, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const optional<bool> &missingBlockIsIntegrityViolation) {
optional<CryConfigLoader::ConfigLoadResult> Cli::_loadOrCreateConfigFile(bf::path configFilePath, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem) {
if (_noninteractive) {
return CryConfigLoader(_console, _keyGenerator, _scryptSettings,
&Cli::_askPasswordNoninteractive,
&Cli::_askPasswordNoninteractive,
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade);
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
} else {
return CryConfigLoader(_console, _keyGenerator, _scryptSettings,
&Cli::_askPasswordForExistingFilesystem,
&Cli::_askPasswordForNewFilesystem,
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade);
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
}
}

View File

@ -24,8 +24,8 @@ namespace cryfs {
void _checkForUpdates(cpputils::unique_ref<cpputils::HttpClient> httpClient);
void _runFilesystem(const program_options::ProgramOptions &options);
CryConfigLoader::ConfigLoadResult _loadOrCreateConfig(const program_options::ProgramOptions &options);
void _checkConfigIntegrity(const boost::filesystem::path& basedir, const CryConfigFile& config);
boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation);
void _checkConfigIntegrity(const boost::filesystem::path& basedir, const CryConfigFile& config, bool allowReplacedFilesystem);
boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, 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();

View File

@ -61,6 +61,7 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
fuseOptions.push_back(const_cast<char*>("-f"));
}
bool allowFilesystemUpgrade = vm.count("allow-filesystem-upgrade");
bool allowReplacedFilesystem = vm.count("allow-replaced-filesystem");
optional<double> unmountAfterIdleMinutes = none;
if (vm.count("unmount-idle")) {
unmountAfterIdleMinutes = vm["unmount-idle"].as<double>();
@ -94,7 +95,7 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
}
}
return ProgramOptions(std::move(baseDir), std::move(mountDir), std::move(configfile), foreground, allowFilesystemUpgrade, std::move(unmountAfterIdleMinutes), std::move(logfile), std::move(cipher), blocksizeBytes, noIntegrityChecks, std::move(missingBlockIsIntegrityViolation), std::move(fuseOptions));
return ProgramOptions(std::move(baseDir), std::move(mountDir), std::move(configfile), foreground, allowFilesystemUpgrade, allowReplacedFilesystem, std::move(unmountAfterIdleMinutes), std::move(logfile), std::move(cipher), blocksizeBytes, noIntegrityChecks, std::move(missingBlockIsIntegrityViolation), std::move(fuseOptions));
}
void Parser::_checkValidCipher(const string &cipher, const vector<string> &supportedCiphers) {
@ -168,6 +169,7 @@ void Parser::_addAllowedOptions(po::options_description *desc) {
("no-integrity-checks", "Disable integrity checks. Integrity checks ensure that your file system was not manipulated or rolled back to an earlier version. Disabling them is needed if you want to load an old snapshot of your file system.")
("missing-block-is-integrity-violation", po::value<bool>(), "Whether to treat a missing block as an integrity violation. This makes sure you notice if an attacker deleted some of your files, but only works in single-client mode. You will not be able to use the file system on other devices.")
("allow-filesystem-upgrade", "Allow upgrading the file system if it was created with an old CryFS version. After the upgrade, older CryFS versions might not be able to use the file system anymore.")
("allow-replaced-filesystem", "By default, CryFS remembers file systems it has seen in this base directory and checks that it didn't get replaced by an attacker with an entirely different file system since the last time it was loaded. However, if you do want to replace the file system with an entirely new one, you can pass in this option to disable the check.")
("show-ciphers", "Show list of supported ciphers.")
("unmount-idle", po::value<double>(), "Automatically unmount after specified number of idle minutes.")
("logfile", po::value<string>(), "Specify the file to write log messages to. If this is not specified, log messages will go to stdout, or syslog if CryFS is running in the background.")

View File

@ -9,13 +9,13 @@ using boost::optional;
namespace bf = boost::filesystem;
ProgramOptions::ProgramOptions(bf::path baseDir, bf::path mountDir, optional<bf::path> configFile,
bool foreground, bool allowFilesystemUpgrade, optional<double> unmountAfterIdleMinutes,
bool foreground, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, optional<double> unmountAfterIdleMinutes,
optional<bf::path> logFile, optional<string> cipher,
optional<uint32_t> blocksizeBytes,
bool noIntegrityChecks,
boost::optional<bool> missingBlockIsIntegrityViolation,
vector<string> fuseOptions)
:_baseDir(std::move(baseDir)), _mountDir(std::move(mountDir)), _configFile(std::move(configFile)), _foreground(foreground), _allowFilesystemUpgrade(allowFilesystemUpgrade), _noIntegrityChecks(noIntegrityChecks),
:_baseDir(std::move(baseDir)), _mountDir(std::move(mountDir)), _configFile(std::move(configFile)), _foreground(foreground), _allowFilesystemUpgrade(allowFilesystemUpgrade), _allowReplacedFilesystem(allowReplacedFilesystem), _noIntegrityChecks(noIntegrityChecks),
_cipher(std::move(cipher)), _blocksizeBytes(std::move(blocksizeBytes)), _unmountAfterIdleMinutes(std::move(unmountAfterIdleMinutes)),
_missingBlockIsIntegrityViolation(std::move(missingBlockIsIntegrityViolation)), _logFile(std::move(logFile)), _fuseOptions(std::move(fuseOptions)) {
}
@ -60,6 +60,10 @@ bool ProgramOptions::noIntegrityChecks() const {
return _noIntegrityChecks;
}
bool ProgramOptions::allowReplacedFilesystem() const {
return _allowReplacedFilesystem;
}
const optional<bool> &ProgramOptions::missingBlockIsIntegrityViolation() const {
return _missingBlockIsIntegrityViolation;
}

View File

@ -14,7 +14,7 @@ namespace cryfs {
public:
ProgramOptions(boost::filesystem::path baseDir, boost::filesystem::path mountDir,
boost::optional<boost::filesystem::path> configFile,
bool foreground, bool allowFilesystemUpgrade, boost::optional<double> unmountAfterIdleMinutes,
bool foreground, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, boost::optional<double> unmountAfterIdleMinutes,
boost::optional<boost::filesystem::path> logFile,
boost::optional<std::string> cipher,
boost::optional<uint32_t> blocksizeBytes,
@ -28,6 +28,7 @@ namespace cryfs {
const boost::optional<boost::filesystem::path> &configFile() const;
bool foreground() const;
bool allowFilesystemUpgrade() const;
bool allowReplacedFilesystem() const;
const boost::optional<std::string> &cipher() const;
const boost::optional<uint32_t> &blocksizeBytes() const;
const boost::optional<double> &unmountAfterIdleMinutes() const;
@ -42,6 +43,7 @@ namespace cryfs {
boost::optional<boost::filesystem::path> _configFile;
bool _foreground;
bool _allowFilesystemUpgrade;
bool _allowReplacedFilesystem;
bool _noIntegrityChecks;
boost::optional<std::string> _cipher;
boost::optional<uint32_t> _blocksizeBytes;

View File

@ -40,10 +40,10 @@ enum class ErrorCode : int {
// Something's wrong with the file system.
InvalidFilesystem = 19,
// The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This could mean an attacker replaced the file system with a different one.
// The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this.
FilesystemIdChanged = 20,
// The filesystem encryption key differs from the last time we loaded this filesystem. This could mean an attacker replaced the file system with a different one.
// The filesystem encryption key differs from the last time we loaded this filesystem. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this.
EncryptionKeyChanged = 21,
// The command line options and the file system disagree on whether missing blocks should be treated as integrity violations.

View File

@ -19,7 +19,7 @@ namespace cryfs {
:_console(console), _configConsole(console), _encryptionKeyGenerator(encryptionKeyGenerator) {
}
CryConfigCreator::ConfigCreateResult CryConfigCreator::create(const optional<string> &cipherFromCommandLine, const optional<uint32_t> &blocksizeBytesFromCommandLine, const optional<bool> &missingBlockIsIntegrityViolationFromCommandLine) {
CryConfigCreator::ConfigCreateResult CryConfigCreator::create(const optional<string> &cipherFromCommandLine, const optional<uint32_t> &blocksizeBytesFromCommandLine, const optional<bool> &missingBlockIsIntegrityViolationFromCommandLine, bool allowReplacedFilesystem) {
CryConfig config;
config.SetCipher(_generateCipher(cipherFromCommandLine));
config.SetVersion(gitversion::VersionString());
@ -28,7 +28,7 @@ namespace cryfs {
config.SetRootBlob(_generateRootBlobId());
config.SetFilesystemId(_generateFilesystemID());
auto encryptionKey = _generateEncKey(config.Cipher());
auto localState = LocalStateMetadata::loadOrGenerate(LocalStateDir::forFilesystemId(config.FilesystemId()), cpputils::Data::FromString(encryptionKey));
auto localState = LocalStateMetadata::loadOrGenerate(LocalStateDir::forFilesystemId(config.FilesystemId()), cpputils::Data::FromString(encryptionKey), allowReplacedFilesystem);
uint32_t myClientId = localState.myClientId();
config.SetEncryptionKey(std::move(encryptionKey));
config.SetExclusiveClientId(_generateExclusiveClientId(missingBlockIsIntegrityViolationFromCommandLine, myClientId));

View File

@ -19,7 +19,7 @@ namespace cryfs {
uint32_t myClientId;
};
ConfigCreateResult create(const boost::optional<std::string> &cipherFromCommandLine, const boost::optional<uint32_t> &blocksizeBytesFromCommandLine, const boost::optional<bool> &missingBlockIsIntegrityViolationFromCommandLine);
ConfigCreateResult create(const boost::optional<std::string> &cipherFromCommandLine, const boost::optional<uint32_t> &blocksizeBytesFromCommandLine, const boost::optional<bool> &missingBlockIsIntegrityViolationFromCommandLine, bool allowReplacedFilesystem);
private:
std::string _generateCipher(const boost::optional<std::string> &cipherFromCommandLine);
std::string _generateEncKey(const std::string &cipher);

View File

@ -32,7 +32,7 @@ CryConfigLoader::CryConfigLoader(shared_ptr<Console> console, RandomGenerator &k
_missingBlockIsIntegrityViolationFromCommandLine(missingBlockIsIntegrityViolationFromCommandLine) {
}
optional<CryConfigLoader::ConfigLoadResult> CryConfigLoader::_loadConfig(bf::path filename, bool allowFilesystemUpgrade) {
optional<CryConfigLoader::ConfigLoadResult> CryConfigLoader::_loadConfig(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) {
string password = _askPasswordForExistingFilesystem();
std::cout << "Loading config file (this can take some time)..." << std::flush;
auto config = CryConfigFile::load(std::move(filename), password);
@ -52,7 +52,7 @@ optional<CryConfigLoader::ConfigLoadResult> CryConfigLoader::_loadConfig(bf::pat
config->save();
}
_checkCipher(*config->config());
auto localState = LocalStateMetadata::loadOrGenerate(LocalStateDir::forFilesystemId(config->config()->FilesystemId()), cpputils::Data::FromString(config->config()->EncryptionKey()));
auto localState = LocalStateMetadata::loadOrGenerate(LocalStateDir::forFilesystemId(config->config()->FilesystemId()), cpputils::Data::FromString(config->config()->EncryptionKey()), allowReplacedFilesystem);
uint32_t myClientId = localState.myClientId();
_checkMissingBlocksAreIntegrityViolations(&*config, myClientId);
return ConfigLoadResult {std::move(*config), myClientId};
@ -96,16 +96,16 @@ void CryConfigLoader::_checkMissingBlocksAreIntegrityViolations(CryConfigFile *c
}
}
optional<CryConfigLoader::ConfigLoadResult> CryConfigLoader::loadOrCreate(bf::path filename, bool allowFilesystemUpgrade) {
optional<CryConfigLoader::ConfigLoadResult> CryConfigLoader::loadOrCreate(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) {
if (bf::exists(filename)) {
return _loadConfig(std::move(filename), allowFilesystemUpgrade);
return _loadConfig(std::move(filename), allowFilesystemUpgrade, allowReplacedFilesystem);
} else {
return _createConfig(std::move(filename));
return _createConfig(std::move(filename), allowReplacedFilesystem);
}
}
CryConfigLoader::ConfigLoadResult CryConfigLoader::_createConfig(bf::path filename) {
auto config = _creator.create(_cipherFromCommandLine, _blocksizeBytesFromCommandLine, _missingBlockIsIntegrityViolationFromCommandLine);
CryConfigLoader::ConfigLoadResult CryConfigLoader::_createConfig(bf::path filename, bool allowReplacedFilesystem) {
auto config = _creator.create(_cipherFromCommandLine, _blocksizeBytesFromCommandLine, _missingBlockIsIntegrityViolationFromCommandLine, allowReplacedFilesystem);
//TODO Ask confirmation if using insecure password (<8 characters)
string password = _askPasswordForNewFilesystem();
std::cout << "Creating config file (this can take some time)..." << std::flush;

View File

@ -21,11 +21,11 @@ public:
uint32_t myClientId;
};
boost::optional<ConfigLoadResult> loadOrCreate(boost::filesystem::path filename, bool allowFilesystemUpgrade);
boost::optional<ConfigLoadResult> loadOrCreate(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem);
private:
boost::optional<ConfigLoadResult> _loadConfig(boost::filesystem::path filename, bool allowFilesystemUpgrade);
ConfigLoadResult _createConfig(boost::filesystem::path filename);
boost::optional<ConfigLoadResult> _loadConfig(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem);
ConfigLoadResult _createConfig(boost::filesystem::path filename, bool allowReplacedFilesystem);
void _checkVersion(const CryConfig &config, bool allowFilesystemUpgrade);
void _checkCipher(const CryConfig &config) const;
void _checkMissingBlocksAreIntegrityViolations(CryConfigFile *configFile, uint32_t myClientId);

View File

@ -27,7 +27,7 @@ namespace cryfs {
LocalStateMetadata::LocalStateMetadata(uint32_t myClientId, Hash encryptionKeyHash)
: _myClientId(myClientId), _encryptionKeyHash(encryptionKeyHash) {}
LocalStateMetadata LocalStateMetadata::loadOrGenerate(const bf::path &statePath, const Data& encryptionKey) {
LocalStateMetadata LocalStateMetadata::loadOrGenerate(const bf::path &statePath, const Data& encryptionKey, bool allowReplacedFilesystem) {
auto metadataFile = statePath / "metadata";
auto loaded = _load(metadataFile);
if (loaded == none) {
@ -35,7 +35,7 @@ LocalStateMetadata LocalStateMetadata::loadOrGenerate(const bf::path &statePath,
return _generate(metadataFile, encryptionKey);
}
if (loaded->_encryptionKeyHash.digest != cpputils::hash::hash(encryptionKey, loaded->_encryptionKeyHash.salt).digest) {
if (!allowReplacedFilesystem && loaded->_encryptionKeyHash.digest != cpputils::hash::hash(encryptionKey, loaded->_encryptionKeyHash.salt).digest) {
throw CryfsException("The filesystem encryption key differs from the last time we loaded this filesystem. Did an attacker replace the file system?", ErrorCode::EncryptionKeyChanged);
}
return *loaded;

View File

@ -12,7 +12,7 @@ namespace cryfs {
class LocalStateMetadata final {
public:
static LocalStateMetadata loadOrGenerate(const boost::filesystem::path &statePath, const cpputils::Data& encryptionKey);
static LocalStateMetadata loadOrGenerate(const boost::filesystem::path &statePath, const cpputils::Data& encryptionKey, bool allowReplacedFilesystem);
uint32_t myClientId() const;