176 lines
6.6 KiB
C++
176 lines
6.6 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).right_opt().value();
|
|
configFile->config()->SetFilesystemId(CryConfig::FilesystemID::FromString("0123456789ABCDEF0123456789ABCDEF"));
|
|
configFile->save();
|
|
}
|
|
|
|
void modifyFilesystemKey() {
|
|
FakeCryKeyProvider keyProvider;
|
|
auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider).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);
|
|
}
|
|
|
|
}
|