libcryfs/test/cryfs-cli/CliTest_IntegrityCheck.cpp
Sebastian Messmer 3938942a02 - cryfs-stats tool is guaranteed to be readonly and not modify the file system
- Now shows a better error message when failing to load the config file and distinguishes between 'wrong password' and 'config file not found'
- The cryfs-stats tool only reads and never writes the cryfs.config file
2020-07-24 16:14:53 -07:00

176 lines
6.7 KiB
C++

#include "testutils/CliTest.h"
#include <cryfs/impl/config/CryConfigFile.h>
#include <cryfs/impl/ErrorCodes.h>
#include <cpp-utils/crypto/kdf/Scrypt.h>
#include <cpp-utils/data/DataFixture.h>
#include <cpp-utils/tempfile/TempDir.h>
#include <blockstore/implementations/caching/CachingBlockStore2.h>
#include <cryfs/impl/filesystem/cachingfsblobstore/CachingFsBlobStore.h>
using std::vector;
using std::string;
using cryfs::CryConfig;
using cryfs::CryConfigFile;
using cryfs::ErrorCode;
using cryfs::CryKeyProvider;
using cpputils::Data;
using cpputils::EncryptionKey;
using cpputils::SCrypt;
using cpputils::TempDir;
namespace bf = boost::filesystem;
namespace {
void writeFile(const bf::path& filename, const string& content) {
std::ofstream file(filename.c_str(), std::ios::trunc);
file << content;
ASSERT(file.good(), "Failed writing file to file system");
}
bool readingFileIsSuccessful(const bf::path& filename) {
std::ifstream file(filename.c_str());
std::string content;
file >> content; // just read a little bit so we have a file access
return file.good();
}
void recursive_copy(const bf::path &src, const bf::path &dst) {
if (bf::exists(dst)) {
throw std::runtime_error(dst.generic_string() + " already exists");
}
if (bf::is_directory(src)) {
bf::create_directories(dst);
for (auto& item : bf::directory_iterator(src)) {
recursive_copy(item.path(), dst / item.path().filename());
}
} else if (bf::is_regular_file(src)) {
bf::copy_file(src, dst);
} else {
throw std::runtime_error(dst.generic_string() + " neither dir nor file");
}
}
class FakeCryKeyProvider final : public CryKeyProvider {
EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const Data &kdfParameters) override {
return SCrypt(SCrypt::TestSettings).deriveExistingKey(keySize, "pass", kdfParameters);
}
KeyResult requestKeyForNewFilesystem(size_t keySize) override {
auto derived = SCrypt(SCrypt::TestSettings).deriveNewKey(keySize, "pass");
return {
std::move(derived.key),
std::move(derived.kdfParameters)
};
}
};
class CliTest_IntegrityCheck : public CliTest {
public:
void modifyFilesystemId() {
FakeCryKeyProvider keyProvider;
auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider, CryConfigFile::Access::ReadWrite).right_opt().value();
configFile->config()->SetFilesystemId(CryConfig::FilesystemID::FromString("0123456789ABCDEF0123456789ABCDEF"));
configFile->save();
}
void modifyFilesystemKey() {
FakeCryKeyProvider keyProvider;
auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider, CryConfigFile::Access::ReadWrite).right_opt().value();
configFile->config()->SetEncryptionKey("0123456789ABCDEF0123456789ABCDEF");
configFile->save();
}
};
TEST_F(CliTest_IntegrityCheck, givenIncorrectFilesystemId_thenFails) {
vector<string> args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"};
//TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that
EXPECT_RUN_SUCCESS(args, mountdir);
modifyFilesystemId();
EXPECT_RUN_ERROR(
args,
"Error 20: The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir.",
ErrorCode::FilesystemIdChanged
);
}
TEST_F(CliTest_IntegrityCheck, givenIncorrectFilesystemKey_thenFails) {
vector<string> args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"};
//TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that
EXPECT_RUN_SUCCESS(args, mountdir);
modifyFilesystemKey();
EXPECT_RUN_ERROR(
args,
"Error 21: The filesystem encryption key differs from the last time we loaded this filesystem. Did an attacker replace the file system?",
ErrorCode::EncryptionKeyChanged
);
}
// TODO Also enable this
TEST_F(CliTest_IntegrityCheck, givenFilesystemWithRolledBackBasedir_whenMounting_thenFails) {
vector<string> args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"};
//TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS/EXPECT_RUN_ERROR can handle that
// create a filesystem with one file
EXPECT_RUN_SUCCESS(args, mountdir, [&] {
writeFile(mountdir / "myfile", "hello world");
});
// backup the base directory
TempDir backup;
recursive_copy(basedir, backup.path() / "basedir");
// modify the file system contents
EXPECT_RUN_SUCCESS(args, mountdir, [&] {
writeFile(mountdir / "myfile", "hello world 2");
});
// roll back base directory
bf::remove_all(basedir);
recursive_copy(backup.path() / "basedir", basedir);
// error code is success because it unmounts normally
EXPECT_RUN_ERROR(args, "Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation, [&] {
EXPECT_FALSE(readingFileIsSuccessful(mountdir / "myfile"));
});
// Test it doesn't mount anymore now because it's marked with an integrity violation
EXPECT_RUN_ERROR(args, "There was an integrity violation detected. Preventing any further access to the file system.", ErrorCode::IntegrityViolationOnPreviousRun);
}
TEST_F(CliTest_IntegrityCheck, whenRollingBackBasedirWhileMounted_thenUnmounts) {
vector<string> args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"};
//TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS/EXPECT_RUN_ERROR can handle that
// create a filesystem with one file
EXPECT_RUN_SUCCESS(args, mountdir, [&] {
writeFile(mountdir / "myfile", "hello world");
});
// backup the base directory
TempDir backup;
recursive_copy(basedir, backup.path() / "basedir");
EXPECT_RUN_ERROR(args, "Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation, [&] {
// modify the file system contents
writeFile(mountdir / "myfile", "hello world 2");
ASSERT(readingFileIsSuccessful(mountdir / "myfile"), ""); // just to make sure reading usually works
// wait for cache timeout (i.e. flush file system to disk)
constexpr auto cache_timeout = blockstore::caching::CachingBlockStore2::MAX_LIFETIME_SEC + cryfs::cachingfsblobstore::CachingFsBlobStore::MAX_LIFETIME_SEC;
boost::this_thread::sleep_for(boost::chrono::seconds(static_cast<int>(std::ceil(cache_timeout * 3))));
// roll back base directory
bf::remove_all(basedir);
recursive_copy(backup.path() / "basedir", basedir);
// expect reading now fails
EXPECT_FALSE(readingFileIsSuccessful(mountdir / "myfile"));
});
// Test it doesn't mount anymore now because it's marked with an integrity violation
EXPECT_RUN_ERROR(args, "There was an integrity violation detected. Preventing any further access to the file system.", ErrorCode::IntegrityViolationOnPreviousRun);
}
}