diff --git a/src/cryfs/config/CryConfig.cpp b/src/cryfs/config/CryConfig.cpp index 7f193b66..45bec926 100644 --- a/src/cryfs/config/CryConfig.cpp +++ b/src/cryfs/config/CryConfig.cpp @@ -15,11 +15,11 @@ using cpputils::Data; namespace cryfs { CryConfig::CryConfig() -: _rootBlob(""), _encKey(""), _cipher(""), _version("") { +: _rootBlob(""), _encKey(""), _cipher(""), _version(""), _blocksizeBytes(0) { } CryConfig::CryConfig(CryConfig &&rhs) -: _rootBlob(std::move(rhs._rootBlob)), _encKey(std::move(rhs._encKey)), _cipher(std::move(rhs._cipher)), _version(std::move(rhs._version)) { +: _rootBlob(std::move(rhs._rootBlob)), _encKey(std::move(rhs._encKey)), _cipher(std::move(rhs._cipher)), _version(std::move(rhs._version)), _blocksizeBytes(rhs._blocksizeBytes) { } CryConfig CryConfig::load(const Data &data) { @@ -33,6 +33,7 @@ CryConfig CryConfig::load(const Data &data) { cfg._encKey = pt.get("cryfs.key", ""); cfg._cipher = pt.get("cryfs.cipher", ""); cfg._version = pt.get("cryfs.version", "0.8"); // CryFS 0.8 didn't specify this field, so if the field doesn't exist, it's 0.8. + cfg._blocksizeBytes = pt.get("cryfs.blocksizeBytes", 32 * 1024); // TODO Put here the actual block size value of earlier CryFS versions return cfg; } @@ -43,6 +44,7 @@ Data CryConfig::save() const { pt.put("cryfs.key", _encKey); pt.put("cryfs.cipher", _cipher); pt.put("cryfs.version", _version); + pt.put("cryfs.blocksizeBytes", _blocksizeBytes); stringstream stream; write_json(stream, pt); @@ -81,4 +83,12 @@ void CryConfig::SetVersion(const std::string &value) { _version = value; } +uint32_t CryConfig::BlocksizeBytes() const { + return _blocksizeBytes; +} + +void CryConfig::SetBlocksizeBytes(uint32_t value) { + _blocksizeBytes = value; +} + } diff --git a/src/cryfs/config/CryConfig.h b/src/cryfs/config/CryConfig.h index b53e63e9..c3a01f92 100644 --- a/src/cryfs/config/CryConfig.h +++ b/src/cryfs/config/CryConfig.h @@ -27,6 +27,9 @@ public: const std::string &Version() const; void SetVersion(const std::string &value); + uint32_t BlocksizeBytes() const; + void SetBlocksizeBytes(uint32_t value); + static CryConfig load(const cpputils::Data &data); cpputils::Data save() const; @@ -35,6 +38,7 @@ private: std::string _encKey; std::string _cipher; std::string _version; + uint32_t _blocksizeBytes; DISALLOW_COPY_AND_ASSIGN(CryConfig); }; diff --git a/src/cryfs/config/CryConfigConsole.cpp b/src/cryfs/config/CryConfigConsole.cpp index 0a57c53b..c1316926 100644 --- a/src/cryfs/config/CryConfigConsole.cpp +++ b/src/cryfs/config/CryConfigConsole.cpp @@ -11,6 +11,7 @@ using std::shared_ptr; namespace cryfs { constexpr const char *CryConfigConsole::DEFAULT_CIPHER; + constexpr uint32_t CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES; CryConfigConsole::CryConfigConsole(shared_ptr console, bool noninteractive) : _console(std::move(console)), _useDefaultSettings(noninteractive ? optional(true) : none) { @@ -45,6 +46,28 @@ namespace cryfs { return _console->askYesNo(string() + (*warning) + " Do you want to take this cipher nevertheless?"); } + uint32_t CryConfigConsole::askBlocksizeBytes() { + if (_checkUseDefaultSettings()) { + return DEFAULT_BLOCKSIZE_BYTES; + } else { + return _askBlocksizeBytes(); + } + } + + uint32_t CryConfigConsole::_askBlocksizeBytes() const { + vector sizes = {"8KB", "32KB", "64KB", "512KB", "1MB", "4MB"}; + int index = _console->ask("Which block size do you want to use?", sizes); + switch(index) { + case 0: return 8*1024; + case 1: return 32*1024; + case 2: return 64*1024; + case 3: return 512*1024; + case 4: return 1024*1024; + case 5: return 4*1024*1024; + default: ASSERT(false, "Unhandled case"); + } + } + bool CryConfigConsole::_checkUseDefaultSettings() { if (_useDefaultSettings == none) { _useDefaultSettings = _console->askYesNo("Use default settings?"); diff --git a/src/cryfs/config/CryConfigConsole.h b/src/cryfs/config/CryConfigConsole.h index 4cee68f6..4156a115 100644 --- a/src/cryfs/config/CryConfigConsole.h +++ b/src/cryfs/config/CryConfigConsole.h @@ -13,8 +13,10 @@ namespace cryfs { CryConfigConsole(CryConfigConsole &&rhs) = default; std::string askCipher(); + uint32_t askBlocksizeBytes(); static constexpr const char *DEFAULT_CIPHER = "aes-256-gcm"; + static constexpr uint32_t DEFAULT_BLOCKSIZE_BYTES = 32 * 1024; // 32KB private: @@ -22,6 +24,7 @@ namespace cryfs { std::string _askCipher() const; bool _showWarningForCipherAndReturnIfOk(const std::string &cipherName) const; + uint32_t _askBlocksizeBytes() const; std::shared_ptr _console; boost::optional _useDefaultSettings; diff --git a/src/cryfs/config/CryConfigCreator.cpp b/src/cryfs/config/CryConfigCreator.cpp index a85e1a39..f6971d44 100644 --- a/src/cryfs/config/CryConfigCreator.cpp +++ b/src/cryfs/config/CryConfigCreator.cpp @@ -20,12 +20,17 @@ namespace cryfs { CryConfig CryConfigCreator::create(const optional &cipherFromCommandLine) { CryConfig config; config.SetCipher(_generateCipher(cipherFromCommandLine)); - config.SetEncryptionKey(_generateEncKey(config.Cipher())); - config.SetRootBlob(_generateRootBlobKey()); config.SetVersion(version::VERSION_STRING); + config.SetBlocksizeBytes(_generateBlocksizeBytes()); + config.SetRootBlob(_generateRootBlobKey()); + config.SetEncryptionKey(_generateEncKey(config.Cipher())); return config; } + uint32_t CryConfigCreator::_generateBlocksizeBytes() { + return _configConsole.askBlocksizeBytes(); + } + string CryConfigCreator::_generateCipher(const optional &cipherFromCommandLine) { if (cipherFromCommandLine != none) { ASSERT(std::find(CryCiphers::supportedCipherNames().begin(), CryCiphers::supportedCipherNames().end(), *cipherFromCommandLine) != CryCiphers::supportedCipherNames().end(), "Invalid cipher"); diff --git a/src/cryfs/config/CryConfigCreator.h b/src/cryfs/config/CryConfigCreator.h index 112f65b0..c3c57b27 100644 --- a/src/cryfs/config/CryConfigCreator.h +++ b/src/cryfs/config/CryConfigCreator.h @@ -19,6 +19,7 @@ namespace cryfs { std::string _generateCipher(const boost::optional &cipherFromCommandLine); std::string _generateEncKey(const std::string &cipher); std::string _generateRootBlobKey(); + uint32_t _generateBlocksizeBytes(); std::shared_ptr _console; CryConfigConsole _configConsole; diff --git a/src/stats/main.cpp b/src/stats/main.cpp new file mode 100644 index 00000000..f40f331d --- /dev/null +++ b/src/stats/main.cpp @@ -0,0 +1,146 @@ +#include +#include +#include "../impl/config/CryConfigFile.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost; +using namespace boost::filesystem; +using namespace std; +using namespace cryfs; +using namespace cpputils; +using namespace blockstore; +using namespace blockstore::ondisk; +using namespace blobstore::onblocks; +using namespace blobstore::onblocks::datanodestore; +using namespace cryfs::fsblobstore; + +void printNode(unique_ref node) { + std::cout << "Key: " << node->key().ToString() << ", Depth: " << node->depth() << " "; + auto innerNode = dynamic_pointer_move(node); + if (innerNode != none) { + std::cout << "Type: inner\n"; + return; + } + auto leafNode = dynamic_pointer_move(node); + if (leafNode != none) { + std::cout << "Type: leaf\n"; + return; + } +} + +set _getBlockstoreUnaccountedBlocks(const CryConfig &config) { + auto onDiskBlockStore = make_unique_ref("/home/heinzi/basedir"); + auto encryptedBlockStore = CryCiphers::find(config.Cipher()).createEncryptedBlockstore(std::move(onDiskBlockStore), config.EncryptionKey()); + auto nodeStore = make_unique_ref(std::move(encryptedBlockStore), CryDevice::BLOCKSIZE_BYTES); + std::set unaccountedBlocks; + uint32_t numBlocks = nodeStore->numNodes(); + uint32_t i = 0; + cout << "There are " << nodeStore->numNodes() << " blocks." << std::endl; + // Add all blocks to unaccountedBlocks + for (auto file = directory_iterator("/home/heinzi/basedir"); file != directory_iterator(); ++file) { + cout << "\r" << (++i) << "/" << numBlocks << flush; + if (file->path().filename() != "cryfs.config") { + auto key = Key::FromString(file->path().filename().c_str()); + unaccountedBlocks.insert(key); + } + } + i = 0; + cout << "\nRemove blocks that have a parent" << endl; + //Remove root block from unaccountedBlocks + unaccountedBlocks.erase(Key::FromString(config.RootBlob())); + //Remove all blocks that have a parent node from unaccountedBlocks + for (auto file = directory_iterator("/home/heinzi/basedir"); file != directory_iterator(); ++file) { + cout << "\r" << (++i) << "/" << numBlocks << flush; + if (file->path().filename() != "cryfs.config") { + auto key = Key::FromString(file->path().filename().c_str()); + auto node = nodeStore->load(key); + auto innerNode = dynamic_pointer_move(*node); + if (innerNode != none) { + for (uint32_t childIndex = 0; childIndex < (*innerNode)->numChildren(); ++childIndex) { + auto child = (*innerNode)->getChild(childIndex)->key(); + unaccountedBlocks.erase(child); + } + } + } + } + return unaccountedBlocks; +} + +set _getBlocksReferencedByDirEntries(const CryConfig &config) { + auto onDiskBlockStore = make_unique_ref("/home/heinzi/basedir"); + auto encryptedBlockStore = CryCiphers::find(config.Cipher()).createEncryptedBlockstore(std::move(onDiskBlockStore), config.EncryptionKey()); + auto fsBlobStore = make_unique_ref(make_unique_ref(std::move(encryptedBlockStore), CryDevice::BLOCKSIZE_BYTES)); + set blocksReferencedByDirEntries; + uint32_t numBlocks = fsBlobStore->numBlocks(); + uint32_t i = 0; + cout << "\nRemove blocks referenced by dir entries" << endl; + for (auto file = directory_iterator("/home/heinzi/basedir"); file != directory_iterator(); ++file) { + cout << "\r" << (++i) << "/" << numBlocks << flush; + if (file->path().filename() != "cryfs.config") { + auto key = Key::FromString(file->path().filename().c_str()); + try { + auto blob = fsBlobStore->load(key); + if (blob != none) { + auto dir = dynamic_pointer_move(*blob); + if (dir != none) { + vector children; + (*dir)->AppendChildrenTo(&children); + for (const auto &child : children) { + blocksReferencedByDirEntries.insert((*dir)->GetChild(child.name)->key); + } + } + } + } catch (...) {} + } + } + return blocksReferencedByDirEntries; +} + + +int main() { + cout << "Password: "; + string password; + getline(cin, password); + cout << "Loading config" << endl; + auto config = CryConfigFile::load("/home/heinzi/basedir/cryfs.config", password); + set unaccountedBlocks = _getBlockstoreUnaccountedBlocks(*config->config()); + //Remove all blocks that are referenced by a directory entry from unaccountedBlocks + set blocksReferencedByDirEntries = _getBlocksReferencedByDirEntries(*config->config()); + for (const auto &key : blocksReferencedByDirEntries) { + unaccountedBlocks.erase(key); + } + + cout << "\nCalculate statistics" << endl; + + auto onDiskBlockStore = make_unique_ref("/home/heinzi/basedir"); + auto encryptedBlockStore = CryCiphers::find(config->config()->Cipher()).createEncryptedBlockstore(std::move(onDiskBlockStore), config->config()->EncryptionKey()); + auto nodeStore = make_unique_ref(std::move(encryptedBlockStore), CryDevice::BLOCKSIZE_BYTES); + + uint32_t numUnaccountedBlocks = unaccountedBlocks.size(); + uint32_t numLeaves = 0; + uint32_t numInner = 0; + cout << "\nUnaccounted blocks: " << unaccountedBlocks.size() << endl; + for (const auto &key : unaccountedBlocks) { + std::cout << "\r" << (numLeaves+numInner) << "/" << numUnaccountedBlocks << flush; + auto node = nodeStore->load(key); + auto innerNode = dynamic_pointer_move(*node); + if (innerNode != none) { + ++numInner; + printNode(std::move(*innerNode)); + } + auto leafNode = dynamic_pointer_move(*node); + if (leafNode != none) { + ++numLeaves; + printNode(std::move(*leafNode)); + } + } + cout << "\n" << numLeaves << " leaves and " << numInner << " inner nodes" << endl; +} \ No newline at end of file diff --git a/test/cryfs/config/CryConfigConsoleTest.cpp b/test/cryfs/config/CryConfigConsoleTest.cpp index 8c471609..4a8be8d8 100644 --- a/test/cryfs/config/CryConfigConsoleTest.cpp +++ b/test/cryfs/config/CryConfigConsoleTest.cpp @@ -42,6 +42,10 @@ class CryConfigConsoleTest_Cipher: public CryConfigConsoleTest {}; EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, ask(HasSubstr("block cipher"), UnorderedElementsAreArray(CryCiphers::supportedCipherNames()))).Times(1) +#define EXPECT_ASK_FOR_BLOCKSIZE() \ + EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(1).WillOnce(Return(false)); \ + EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(1) + TEST_F(CryConfigConsoleTest_Cipher, AsksForCipher) { EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseAnyCipher()); cryconsole.askCipher(); @@ -61,6 +65,18 @@ TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultCipherWhenNoninteractiveEnviron EXPECT_EQ(CryConfigConsole::DEFAULT_CIPHER, cipher); } +TEST_F(CryConfigConsoleTest_Cipher, AsksForBlocksize) { + EXPECT_ASK_FOR_BLOCKSIZE().WillOnce(Return(0)); + cryconsole.askBlocksizeBytes(); +} + +TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultBlocksizeWhenNoninteractiveEnvironment) { + EXPECT_CALL(*console, askYesNo(HasSubstr("default"))).Times(0); + EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(0); + uint32_t blocksize = noninteractiveCryconsole.askBlocksizeBytes(); + EXPECT_EQ(CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES, blocksize); +} + class CryConfigConsoleTest_Cipher_Choose: public CryConfigConsoleTest_Cipher, public ::testing::WithParamInterface { public: string cipherName = GetParam(); diff --git a/test/cryfs/config/CryConfigCreatorTest.cpp b/test/cryfs/config/CryConfigCreatorTest.cpp index 52aa52c4..b3a87a74 100644 --- a/test/cryfs/config/CryConfigCreatorTest.cpp +++ b/test/cryfs/config/CryConfigCreatorTest.cpp @@ -24,60 +24,110 @@ using ::testing::HasSubstr; using ::testing::UnorderedElementsAreArray; using ::testing::WithParamInterface; +#define EXPECT_ASK_TO_USE_DEFAULT_SETTINGS() \ + EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(1) +#define EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS() \ + EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(0) +#define EXPECT_ASK_FOR_CIPHER() \ + EXPECT_CALL(*console, ask(HasSubstr("block cipher"), UnorderedElementsAreArray(CryCiphers::supportedCipherNames()))).Times(1) +#define EXPECT_DOES_NOT_ASK_FOR_CIPHER() \ + EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).Times(0) +#define EXPECT_ASK_FOR_BLOCKSIZE() \ + EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(1) +#define EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE() \ + EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(0) + class CryConfigCreatorTest: public ::testing::Test { public: CryConfigCreatorTest() : console(make_shared()), creator(console, cpputils::Random::PseudoRandom(), false), noninteractiveCreator(console, cpputils::Random::PseudoRandom(), true) { + EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).WillRepeatedly(ChooseAnyCipher()); + EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).WillRepeatedly(Return(0)); } shared_ptr console; CryConfigCreator creator; CryConfigCreator noninteractiveCreator; + + void AnswerNoToDefaultSettings() { + EXPECT_ASK_TO_USE_DEFAULT_SETTINGS().WillOnce(Return(false)); + } + + void AnswerYesToDefaultSettings() { + EXPECT_ASK_TO_USE_DEFAULT_SETTINGS().WillOnce(Return(true)); + } }; -#define EXPECT_ASK_FOR_CIPHER() \ - EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(1).WillOnce(Return(false)); \ - EXPECT_CALL(*console, ask(HasSubstr("block cipher"), UnorderedElementsAreArray(CryCiphers::supportedCipherNames()))).Times(1) -#define EXPECT_DOES_NOT_ASK_FOR_CIPHER() \ - EXPECT_CALL(*console, askYesNo("Use default settings?")).Times(0); \ - EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).Times(0); - TEST_F(CryConfigCreatorTest, DoesAskForCipherIfNotSpecified) { + AnswerNoToDefaultSettings(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseAnyCipher()); CryConfig config = creator.create(none); } TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfSpecified) { + AnswerNoToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = creator.create(string("aes-256-gcm")); } +TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfUsingDefaultSettings) { + AnswerYesToDefaultSettings(); + EXPECT_DOES_NOT_ASK_FOR_CIPHER(); + CryConfig config = creator.create(none); +} + TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfNoninteractive) { + EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = noninteractiveCreator.create(none); } +TEST_F(CryConfigCreatorTest, DoesAskForBlocksizeIfNotSpecified) { + AnswerNoToDefaultSettings(); + EXPECT_ASK_FOR_BLOCKSIZE().WillOnce(Return(1)); + CryConfig config = creator.create(none); +} + +//TODO DoesNotAskForCipherIfSpecified + +TEST_F(CryConfigCreatorTest, DoesNotAskForBlocksizeIfNoninteractive) { + EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); + EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE(); + CryConfig config = noninteractiveCreator.create(none); +} + +TEST_F(CryConfigCreatorTest, DoesNotAskForBlocksizeIfUsingDefaultSettings) { + AnswerYesToDefaultSettings(); + EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE(); + CryConfig config = creator.create(none); +} + TEST_F(CryConfigCreatorTest, ChoosesEmptyRootBlobId) { - EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseAnyCipher()); + AnswerNoToDefaultSettings(); CryConfig config = creator.create(none); EXPECT_EQ("", config.RootBlob()); // This tells CryFS to create a new root blob } TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_448) { + AnswerNoToDefaultSettings(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("mars-448-gcm")); CryConfig config = creator.create(none); cpputils::Mars448_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_256) { + AnswerNoToDefaultSettings(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("aes-256-gcm")); CryConfig config = creator.create(none); cpputils::AES256_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_128) { + AnswerNoToDefaultSettings(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("aes-128-gcm")); CryConfig config = creator.create(none); cpputils::AES128_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } + +//TODO Add test cases ensuring that the values entered are correctly taken diff --git a/test/cryfs/config/CryConfigTest.cpp b/test/cryfs/config/CryConfigTest.cpp index 41a12337..f677ba5c 100644 --- a/test/cryfs/config/CryConfigTest.cpp +++ b/test/cryfs/config/CryConfigTest.cpp @@ -76,3 +76,45 @@ TEST_F(CryConfigTest, Cipher_AfterSaveAndLoad) { CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("mycipher", loaded.Cipher()); } + +TEST_F(CryConfigTest, Version_Init) { + EXPECT_EQ("", cfg.Version()); +} + +TEST_F(CryConfigTest, Version) { + cfg.SetVersion("0.9.1"); + EXPECT_EQ("0.9.1", cfg.Version()); +} + +TEST_F(CryConfigTest, Version_AfterMove) { + cfg.SetCipher("0.9.1"); + CryConfig moved = std::move(cfg); + EXPECT_EQ("0.9.1", moved.Cipher()); +} + +TEST_F(CryConfigTest, Version_AfterSaveAndLoad) { + cfg.SetCipher("0.9.2"); + CryConfig loaded = SaveAndLoad(std::move(cfg)); + EXPECT_EQ("0.9.2", loaded.Cipher()); +} + +TEST_F(CryConfigTest, BlocksizeBytes_Init) { + EXPECT_EQ(0u, cfg.BlocksizeBytes()); +} + +TEST_F(CryConfigTest, BlocksizeBytes) { + cfg.SetBlocksizeBytes(4*1024*1024); + EXPECT_EQ(4*1024*1024u, cfg.BlocksizeBytes()); +} + +TEST_F(CryConfigTest, BlocksizeBytes_AfterMove) { + cfg.SetBlocksizeBytes(32*1024); + CryConfig moved = std::move(cfg); + EXPECT_EQ(32*1024u, moved.BlocksizeBytes()); +} + +TEST_F(CryConfigTest, BlocksizeBytes_AfterSaveAndLoad) { + cfg.SetBlocksizeBytes(10*1024); + CryConfig loaded = SaveAndLoad(std::move(cfg)); + EXPECT_EQ(10*1024u, loaded.BlocksizeBytes()); +}