diff --git a/src/cryfs-cli/Cli.cpp b/src/cryfs-cli/Cli.cpp index 7dc4be7f..d37e8ee9 100644 --- a/src/cryfs-cli/Cli.cpp +++ b/src/cryfs-cli/Cli.cpp @@ -21,6 +21,8 @@ #include "VersionChecker.h" #include #include +#include +#include #include "Environment.h" //TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member. @@ -195,6 +197,17 @@ namespace cryfs { return *configFile; } + void Cli::_checkConfigIntegrity(const bf::path& basedir, const CryConfigFile& config) { + if (!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 std::runtime_error( + "The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir."); + } + } + // Update local state (or create it if it didn't exist yet) + BasedirMetadata::updateFilesystemIdForBasedir(basedir, config.config()->FilesystemId()); + } + CryConfigLoader::ConfigLoadResult Cli::_loadOrCreateConfig(const ProgramOptions &options) { try { auto configFile = _determineConfigFile(options); @@ -203,6 +216,7 @@ namespace cryfs { std::cerr << "Could not load config file. Did you enter the correct password?" << std::endl; exit(1); } + _checkConfigIntegrity(options.baseDir(), config->configFile); return std::move(*config); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; diff --git a/src/cryfs-cli/Cli.h b/src/cryfs-cli/Cli.h index 84d5a20e..ed32b7c2 100644 --- a/src/cryfs-cli/Cli.h +++ b/src/cryfs-cli/Cli.h @@ -23,6 +23,7 @@ namespace cryfs { void _checkForUpdates(); 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 _loadOrCreateConfigFile(const boost::filesystem::path &configFilePath, const boost::optional &cipher, const boost::optional &blocksizeBytes, const boost::optional &missingBlockIsIntegrityViolation); boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options); static std::string _askPasswordForExistingFilesystem(); diff --git a/src/cryfs/CMakeLists.txt b/src/cryfs/CMakeLists.txt index 10e12c5b..b80877e2 100644 --- a/src/cryfs/CMakeLists.txt +++ b/src/cryfs/CMakeLists.txt @@ -42,6 +42,7 @@ set(LIB_SOURCES filesystem/CryDevice.cpp localstate/LocalStateDir.cpp localstate/LocalStateMetadata.cpp + localstate/BasedirMetadata.cpp ) add_library(${PROJECT_NAME} STATIC ${LIB_SOURCES}) diff --git a/src/cryfs/localstate/BasedirMetadata.cpp b/src/cryfs/localstate/BasedirMetadata.cpp new file mode 100644 index 00000000..854455f1 --- /dev/null +++ b/src/cryfs/localstate/BasedirMetadata.cpp @@ -0,0 +1,82 @@ +#include "BasedirMetadata.h" +#include +#include +#include +#include +#include "LocalStateDir.h" + +namespace bf = boost::filesystem; +using boost::property_tree::ptree; +using boost::property_tree::write_json; +using boost::property_tree::read_json; +using boost::optional; +using boost::none; +using std::ostream; +using std::istream; +using std::ifstream; +using std::ofstream; + +namespace cryfs { + +namespace { +bf::path _localStateConfigFile(const bf::path& basedir) { + std::string basedir_id; + CryptoPP::SHA512 hash; + CryptoPP::StringSource(bf::canonical(basedir).native(), true, + new CryptoPP::HashFilter(hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(basedir_id) + ) + ) + ); + return LocalStateDir::forMapFromBasedirToConfigFiles() / basedir_id; +} + +void _serialize(ostream& stream, const CryConfig::FilesystemID& filesystemId) { + ptree pt; + pt.put("filesystemId", filesystemId.ToString()); + + write_json(stream, pt); +} + +CryConfig::FilesystemID _deserialize(istream& stream) { + ptree pt; + read_json(stream, pt); + + std::string filesystemId = pt.get("filesystemId"); + + return CryConfig::FilesystemID::FromString(filesystemId); +} + +optional _load(const bf::path &metadataFilePath) { + ifstream file(metadataFilePath.native()); + if (!file.good()) { + // State file doesn't exist + return none; + } + return _deserialize(file); +} + +void _save(const bf::path &metadataFilePath, const CryConfig::FilesystemID& filesystemId) { + ofstream file(metadataFilePath.native(), std::ios::trunc); + _serialize(file, filesystemId); +} + +} + +bool BasedirMetadata::filesystemIdForBasedirIsCorrect(const bf::path &basedir, const CryConfig::FilesystemID &filesystemId) { + auto metadataFile = _localStateConfigFile(basedir); + auto loaded = _load(metadataFile); + if (loaded == none) { + // Local state not known. Possibly the file system is currently being created. + return true; + } + return loaded == filesystemId; +} + +void BasedirMetadata::updateFilesystemIdForBasedir(const bf::path &basedir, const CryConfig::FilesystemID &filesystemId) { + auto metadataFile = _localStateConfigFile(basedir); + _save(metadataFile, filesystemId); +} + +} diff --git a/src/cryfs/localstate/BasedirMetadata.h b/src/cryfs/localstate/BasedirMetadata.h new file mode 100644 index 00000000..f77b45f0 --- /dev/null +++ b/src/cryfs/localstate/BasedirMetadata.h @@ -0,0 +1,18 @@ +#pragma once +#ifndef MESSMER_CRYFS_LOCALSTATE_BASEDIRMETADATA_H_ +#define MESSMER_CRYFS_LOCALSTATE_BASEDIRMETADATA_H_ + +#include +#include "../config/CryConfig.h" + +namespace cryfs { + +class BasedirMetadata final { +public: + static bool filesystemIdForBasedirIsCorrect(const boost::filesystem::path &basedir, const CryConfig::FilesystemID &filesystemId); + static void updateFilesystemIdForBasedir(const boost::filesystem::path &basedir, const CryConfig::FilesystemID &filesystemId); +}; + +} + +#endif diff --git a/src/cryfs/localstate/LocalStateDir.cpp b/src/cryfs/localstate/LocalStateDir.cpp index 7bee2b97..fd26ed37 100644 --- a/src/cryfs/localstate/LocalStateDir.cpp +++ b/src/cryfs/localstate/LocalStateDir.cpp @@ -5,19 +5,36 @@ namespace bf = boost::filesystem; namespace cryfs { + namespace { + // TODO constexpr? + bf::path& appDir() { + static bf::path singleton = cpputils::system::HomeDirectory::get() / ".cryfs"; + return singleton; + } + } + bf::path LocalStateDir::forFilesystemId(const CryConfig::FilesystemID &filesystemId) { - bf::path app_dir = cpputils::system::HomeDirectory::get() / ".cryfs"; - _createDirIfNotExists(app_dir); - bf::path filesystems_dir = app_dir / "filesystems"; + _createDirIfNotExists(appDir()); + bf::path filesystems_dir = appDir() / "filesystems"; _createDirIfNotExists(filesystems_dir); bf::path this_filesystem_dir = filesystems_dir / filesystemId.ToString(); _createDirIfNotExists(this_filesystem_dir); return this_filesystem_dir; } + bf::path LocalStateDir::forMapFromBasedirToConfigFiles() { + bf::path result = appDir() / "map_basedir_initialconfigfile"; + _createDirIfNotExists(result); + return result; + } + void LocalStateDir::_createDirIfNotExists(const bf::path &path) { if (!bf::exists(path)) { - bf::create_directory(path); + bf::create_directories(path); } } + + void LocalStateDir::setAppDir(boost::filesystem::path path) { + appDir() = std::move(path); + } } diff --git a/src/cryfs/localstate/LocalStateDir.h b/src/cryfs/localstate/LocalStateDir.h index 481c1d9a..882a4cda 100644 --- a/src/cryfs/localstate/LocalStateDir.h +++ b/src/cryfs/localstate/LocalStateDir.h @@ -11,6 +11,11 @@ namespace cryfs { class LocalStateDir final { public: static boost::filesystem::path forFilesystemId(const CryConfig::FilesystemID &filesystemId); + static boost::filesystem::path forMapFromBasedirToConfigFiles(); + + // Use this from test cases to not pollute local config + // TODO Make test cases call this + static void setAppDir(boost::filesystem::path path); private: LocalStateDir(); // static functions only diff --git a/src/cryfs/localstate/LocalStateMetadata.cpp b/src/cryfs/localstate/LocalStateMetadata.cpp index bcd76624..de78da50 100644 --- a/src/cryfs/localstate/LocalStateMetadata.cpp +++ b/src/cryfs/localstate/LocalStateMetadata.cpp @@ -67,7 +67,7 @@ optional _tryLoadClientIdFromLegacyFile(const bf::path &metadataFilePa uint32_t value; file >> value; file.close(); - //bf::remove(myClientIdFile); + bf::remove(myClientIdFile); return value; } #endif diff --git a/test/cryfs/CMakeLists.txt b/test/cryfs/CMakeLists.txt index 4da693b6..eaf94232 100644 --- a/test/cryfs/CMakeLists.txt +++ b/test/cryfs/CMakeLists.txt @@ -17,7 +17,8 @@ set(SOURCES filesystem/CryFsTest.cpp filesystem/CryNodeTest.cpp filesystem/FileSystemTest.cpp - localstate/LocalStateMetadataTest.cpp + localstate/LocalStateMetadataTest.cpp + localstate/BasedirMetadataTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) diff --git a/test/cryfs/localstate/BasedirMetadataTest.cpp b/test/cryfs/localstate/BasedirMetadataTest.cpp new file mode 100644 index 00000000..cefb22e0 --- /dev/null +++ b/test/cryfs/localstate/BasedirMetadataTest.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include +#include +#include + +using cpputils::TempDir; +using cryfs::BasedirMetadata; +using std::ofstream; +namespace bf = boost::filesystem; +using FilesystemID = cryfs::CryConfig::FilesystemID ; + +class BasedirMetadataTest : public ::testing::Test { +public: + TempDir tempdir; + bf::path basedir1; + bf::path basedir2; + const FilesystemID id1; + const FilesystemID id2; + + BasedirMetadataTest() + : tempdir() + , basedir1(tempdir.path() / "my/basedir") + , basedir2(tempdir.path() / "my/other/basedir") + , id1(FilesystemID::FromString("1491BB4932A389EE14BC7090AC772972")) + , id2(FilesystemID::FromString("A1491BB493214BC7090C772972A389EE")) + { + // Use temporary local state dir to not pollute local state + cryfs::LocalStateDir::setAppDir(tempdir.path() / "appdir"); + // Create basedirs so bf::canonical() works + bf::create_directories(basedir1); + bf::create_directories(basedir2); + } + +}; + +TEST_F(BasedirMetadataTest, givenEmptyState_whenCalled_thenSucceeds) { + EXPECT_TRUE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id1)); +} + +TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledForDifferentBasedir_thenSucceeds) { + BasedirMetadata::updateFilesystemIdForBasedir(basedir2, id1); + EXPECT_TRUE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id1)); +} + +TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledWithSameId_thenSucceeds) { + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id1); + EXPECT_TRUE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id1)); +} + +TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledWithDifferentId_thenFails) { + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id2); + EXPECT_FALSE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id1)); +} + +TEST_F(BasedirMetadataTest, givenStateWithUpdatedBasedir_whenCalledWithSameId_thenSucceeds) { + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id2); + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id1); + EXPECT_TRUE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id1)); +} + +TEST_F(BasedirMetadataTest, givenStateWithUpdatedBasedir_whenCalledWithDifferentId_thenFails) { + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id2); + BasedirMetadata::updateFilesystemIdForBasedir(basedir1, id1); + EXPECT_FALSE(BasedirMetadata::filesystemIdForBasedirIsCorrect(basedir1, id2)); +}