From d1a9ccdd1950a6a764ba33ca5028b45a127f5e30 Mon Sep 17 00:00:00 2001 From: Valient Gough Date: Mon, 21 Oct 2013 05:38:27 +0000 Subject: [PATCH] rework NameIO interface to use string instead of C-strings, avoid leaking memory if error occurs git-svn-id: http://encfs.googlecode.com/svn/trunk@123 db9cf616-1c43-0410-9cb8-a902689de0d6 --- base/Interface.cpp | 7 +- base/Interface.h | 2 +- base/base64.cpp | 16 +- base/base64.h | 9 +- fs/{test_BlockIO.cpp => BlockFileIO_test.cpp} | 0 fs/BlockNameIO.cpp | 95 ++++++------ fs/BlockNameIO.h | 15 +- fs/CMakeLists.txt | 8 +- fs/DirNode.cpp | 16 +- fs/{test_IO.cpp => IO_test.cpp} | 0 fs/NameIO.cpp | 139 ++++++------------ fs/NameIO.h | 63 +++----- fs/NameIO_test.cpp | 59 ++++++++ fs/NullNameIO.cpp | 15 +- fs/NullNameIO.h | 14 +- fs/StreamNameIO.cpp | 96 ++++++------ fs/StreamNameIO.h | 14 +- fs/testing.cpp | 3 + 18 files changed, 265 insertions(+), 306 deletions(-) rename fs/{test_BlockIO.cpp => BlockFileIO_test.cpp} (100%) rename fs/{test_IO.cpp => IO_test.cpp} (100%) create mode 100644 fs/NameIO_test.cpp diff --git a/base/Interface.cpp b/base/Interface.cpp index f7229cd..f8b3eb6 100644 --- a/base/Interface.cpp +++ b/base/Interface.cpp @@ -27,7 +27,10 @@ namespace encfs { -std::ostream &operator<<(std::ostream &out, const Interface &iface) { +using std::string; +using std::ostream; + +ostream &operator<<(ostream &out, const Interface &iface) { out << iface.name() << "(" << iface.major() << ":" << iface.minor() << ":" << iface.age() << ")"; return out; @@ -42,7 +45,7 @@ bool implements(const Interface &A, const Interface &B) { return (currentDiff >= 0 && currentDiff <= (int)A.age()); } -Interface makeInterface(const char *name, int major, int minor, int age) { +Interface makeInterface(const string &name, int major, int minor, int age) { Interface iface; iface.set_name(name); iface.set_major(major); diff --git a/base/Interface.h b/base/Interface.h index d93b1d7..1970c0a 100644 --- a/base/Interface.h +++ b/base/Interface.h @@ -32,7 +32,7 @@ namespace encfs { // compatibility. Even if A.implements(B) is true, B > A may also be // true, meaning B is a newer revision of the interface then A. bool implements(const Interface &a, const Interface &b); -Interface makeInterface(const char *name, int major, int minor, int age); +Interface makeInterface(const std::string &name, int major, int minor, int age); // Read operation class ConfigVar; diff --git a/base/base64.cpp b/base/base64.cpp index 071a989..d5933bf 100644 --- a/base/base64.cpp +++ b/base/base64.cpp @@ -132,11 +132,9 @@ static const byte Ascii2B64Table[] = " 01 23456789:; "; // 0123456789 123456789 123456789 123456789 123456789 123456789 1234 // 0 1 2 3 4 5 6 -void AsciiToB64(byte *in, int length) { return AsciiToB64(in, in, length); } - -void AsciiToB64(byte *out, const byte *in, int length) { +void AsciiToB64(byte *buf, int length) { while (length--) { - byte ch = *in++; + byte ch = *buf; if (ch >= 'A') { if (ch >= 'a') ch += 38 - 'a'; @@ -145,7 +143,7 @@ void AsciiToB64(byte *out, const byte *in, int length) { } else ch = Ascii2B64Table[ch] - '0'; - *out++ = ch; + *buf++ = ch; } } @@ -161,18 +159,16 @@ void B32ToAscii(byte *buf, int len) { } } -void AsciiToB32(byte *in, int length) { return AsciiToB32(in, in, length); } - -void AsciiToB32(byte *out, const byte *in, int length) { +void AsciiToB32(byte *buf, int length) { while (length--) { - byte ch = *in++; + byte ch = *buf; int lch = toupper(ch); if (lch >= 'A') lch -= 'A'; else lch += 26 - '2'; - *out++ = (byte)lch; + *buf++ = (byte)lch; } } diff --git a/base/base64.h b/base/base64.h index 5977c29..36784ee 100644 --- a/base/base64.h +++ b/base/base64.h @@ -54,19 +54,22 @@ void changeBase2Inline(byte *buf, int srcLength, int srcPow2, int dst2Pow, bool outputPartialLastByte); // inplace translation from values [0,2^6] => base64 ASCII +// This is a nonstandard B64 encoding, which uses ',' and '-' as +// non-alphanumeric chars. void B64ToAscii(byte *buf, int length); // inplace translation from values [0,2^5] => base32 ASCII void B32ToAscii(byte *buf, int length); // inplace translation from values base64 ASCII => [0,2^6] +// This is a nonstandard B64 encoding, which uses ',' and '-' as +// non-alphanumeric chars. void AsciiToB64(byte *buf, int length); -void AsciiToB64(byte *out, const byte *in, int length); // inplace translation from values base32 ASCII => [0,2^5] void AsciiToB32(byte *buf, int length); -void AsciiToB32(byte *out, const byte *in, int length); -// Decode B64 standard into the output array. +// Decode standard B64 into the output array. +// Used only to decode legacy Boost XML serialized config format. // The output size must be at least B64ToB256Bytes(inputLen). bool B64StandardDecode(byte *out, const byte *in, int inputLen); diff --git a/fs/test_BlockIO.cpp b/fs/BlockFileIO_test.cpp similarity index 100% rename from fs/test_BlockIO.cpp rename to fs/BlockFileIO_test.cpp diff --git a/fs/BlockNameIO.cpp b/fs/BlockNameIO.cpp index 25248a3..1ad12f8 100644 --- a/fs/BlockNameIO.cpp +++ b/fs/BlockNameIO.cpp @@ -26,10 +26,15 @@ #include "cipher/CipherV1.h" #include +#include + #include namespace encfs { +using std::string; +using std::vector; + static shared_ptr NewBlockNameIO(const Interface &iface, const shared_ptr &cipher) { return shared_ptr(new BlockNameIO(iface, cipher, false)); @@ -67,13 +72,13 @@ static bool BlockIO32_registered = NameIO::Register( - Version 3.0 uses full 64 bit initialization vector during IV chaining. Prior versions used only the output from the MAC_16 call, giving a 1 in 2^16 chance of the same name being produced. Using the full 64 bit IV - changes that to a 1 in 2^64 chance.. + reduces to a 1 in 2^64 chance.. - - Version 4.0 adds support for base32, creating names more suitable for + - Version 4.0 adds support for base32, creating names better suited to case-insensitive filesystems (eg Mac). */ Interface BlockNameIO::CurrentInterface(bool caseSensitive) { - // implement major version 4 plus support for two prior versions + // implement major version 4 plus support for prior versions if (caseSensitive) return makeInterface("nameio/block32", 4, 0, 2); else @@ -113,16 +118,20 @@ int BlockNameIO::maxDecodedNameLen(int encodedNameLen) const { return decLen256 - 2; // 2 checksum bytes removed.. } -int BlockNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const { - // copy the data into the encoding buffer.. - memcpy(encodedName + 2, plaintextName, length); - +string BlockNameIO::encodeName(const string &plaintextName, + uint64_t *iv) const { + int length = plaintextName.length(); // Pad encryption buffer to block boundary.. int padding = _bs - length % _bs; - if (padding == 0) padding = _bs; // padding a full extra block! + int encodedStreamLen = length + 2 + padding; + int encLen = _caseSensitive ? B256ToB32Bytes(encodedStreamLen) + : B256ToB64Bytes(encodedStreamLen); - memset(encodedName + length + 2, (unsigned char)padding, padding); + vector tmpBuf(encLen); + + // copy the data into the encoding buffer.. + memcpy(tmpBuf.data() + 2, plaintextName.data(), length); + memset(tmpBuf.data() + length + 2, (unsigned char)padding, padding); // store the IV before it is modified by the MAC call. uint64_t tmpIV = 0; @@ -130,38 +139,29 @@ int BlockNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv, // include padding in MAC computation unsigned int mac = _cipher->reduceMac16( - _cipher->MAC_64((unsigned char *)encodedName + 2, length + padding, iv)); + _cipher->MAC_64(tmpBuf.data() + 2, length + padding, iv)); + tmpIV ^= (uint64_t)mac; // add checksum bytes - encodedName[0] = (mac >> 8) & 0xff; - encodedName[1] = (mac) & 0xff; + tmpBuf[0] = (mac >> 8) & 0xff; + tmpBuf[1] = (mac) & 0xff; - _cipher->blockEncode((unsigned char *)encodedName + 2, length + padding, - (uint64_t)mac ^ tmpIV); - - // convert to base 64 ascii - int encodedStreamLen = length + 2 + padding; - int encLen; + _cipher->blockEncode(tmpBuf.data() + 2, length + padding, tmpIV); + // convert to base 32 or 64 ascii if (_caseSensitive) { - encLen = B256ToB32Bytes(encodedStreamLen); - - changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 5, - true); - B32ToAscii((unsigned char *)encodedName, encLen); + changeBase2Inline(tmpBuf.data(), encodedStreamLen, 8, 5, true); + B32ToAscii(tmpBuf.data(), encLen); } else { - encLen = B256ToB64Bytes(encodedStreamLen); - - changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6, - true); - B64ToAscii((unsigned char *)encodedName, encLen); + changeBase2Inline(tmpBuf.data(), encodedStreamLen, 8, 6, true); + B64ToAscii(tmpBuf.data(), encLen); } - return encLen; + return string(reinterpret_cast(tmpBuf.data()), encLen); } -int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const { +string BlockNameIO::decodeName(const string &encodedName, uint64_t *iv) const { + int length = encodedName.length(); int decLen256 = _caseSensitive ? B32ToB256Bytes(length) : B64ToB256Bytes(length); int decodedStreamLen = decLen256 - 2; @@ -169,29 +169,30 @@ int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, // don't bother trying to decode files which are too small if (decodedStreamLen < _bs) throw Error("Filename too small to decode"); - BUFFER_INIT(tmpBuf, 32, (unsigned int)length); + vector tmpBuf(length, 0); + memcpy(tmpBuf.data(), encodedName.data(), length); // decode into tmpBuf, if (_caseSensitive) { - AsciiToB32((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); - changeBase2Inline((unsigned char *)tmpBuf, length, 5, 8, false); + AsciiToB32(tmpBuf.data(), length); + changeBase2Inline(tmpBuf.data(), length, 5, 8, false); } else { - AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); - changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false); + AsciiToB64(tmpBuf.data(), length); + changeBase2Inline(tmpBuf.data(), length, 6, 8, false); } // pull out the header information - unsigned int mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8 | - ((unsigned int)((unsigned char)tmpBuf[1])); + unsigned int mac = ((unsigned int)tmpBuf[0]) << 8 | + ((unsigned int)tmpBuf[1]); uint64_t tmpIV = 0; if (iv && _interface >= 3) tmpIV = *iv; + tmpIV ^= (uint64_t)mac; - _cipher->blockDecode((unsigned char *)tmpBuf + 2, decodedStreamLen, - (uint64_t)mac ^ tmpIV); + _cipher->blockDecode(&tmpBuf.at(2), decodedStreamLen, tmpIV); // find out true string length - int padding = (unsigned char)tmpBuf[2 + decodedStreamLen - 1]; + int padding = tmpBuf[2 + decodedStreamLen - 1]; int finalSize = decodedStreamLen - padding; // might happen if there is an error decoding.. @@ -201,15 +202,9 @@ int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, throw Error("invalid padding size"); } - // copy out the result.. - memcpy(plaintextName, tmpBuf + 2, finalSize); - plaintextName[finalSize] = '\0'; - // check the mac unsigned int mac2 = _cipher->reduceMac16( - _cipher->MAC_64((const unsigned char *)tmpBuf + 2, decodedStreamLen, iv)); - - BUFFER_RESET(tmpBuf); + _cipher->MAC_64(&tmpBuf.at(2), decodedStreamLen, iv)); if (mac2 != mac) { LOG(INFO) << "checksum mismatch: expected " << mac << ", got " << mac2 @@ -217,7 +212,7 @@ int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, throw Error("checksum mismatch in filename decode"); } - return finalSize; + return string(reinterpret_cast(&tmpBuf.at(2)), finalSize); } bool BlockNameIO::Enabled() { return true; } diff --git a/fs/BlockNameIO.h b/fs/BlockNameIO.h index 9fb61d7..d8bb04d 100644 --- a/fs/BlockNameIO.h +++ b/fs/BlockNameIO.h @@ -24,6 +24,7 @@ #include "fs/NameIO.h" #include +#include namespace encfs { @@ -42,19 +43,19 @@ class BlockNameIO : public NameIO { bool caseSensitiveEncoding = false); virtual ~BlockNameIO(); - virtual Interface interface() const; + virtual Interface interface() const override; - virtual int maxEncodedNameLen(int plaintextNameLen) const; - virtual int maxDecodedNameLen(int encodedNameLen) const; + virtual int maxEncodedNameLen(int plaintextNameLen) const override; + virtual int maxDecodedNameLen(int encodedNameLen) const override; // hack to help with static builds static bool Enabled(); protected: - virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const; - virtual int decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const; + virtual std::string encodeName(const std::string &plaintextName, + uint64_t *iv) const override; + virtual std::string decodeName(const std::string &encodedName, + uint64_t *iv) const override; private: int _interface; diff --git a/fs/CMakeLists.txt b/fs/CMakeLists.txt index bb73eb4..7bdcc76 100644 --- a/fs/CMakeLists.txt +++ b/fs/CMakeLists.txt @@ -41,12 +41,14 @@ if (GTEST_FOUND) link_directories (${PROJECT_BINARY_DIR}/cipher) include_directories (${GTEST_INCLUDE_DIR}) + + file (GLOB TEST_FILES "*_test.cpp") + add_executable (fs-tests MemBlockFileIO.cpp MemFileIO.cpp testing.cpp - test_IO.cpp - test_BlockIO.cpp + ${TEST_FILES} ) target_link_libraries (fs-tests @@ -58,5 +60,5 @@ if (GTEST_FOUND) ) add_test (FSTests fs-tests) - GTEST_ADD_TESTS (fs-tests "${FSTestArgs}" test_IO.cpp test_BlockIO.cpp) + GTEST_ADD_TESTS (fs-tests "${FSTestArgs}" ${TEST_FILES}) endif (GTEST_FOUND) diff --git a/fs/DirNode.cpp b/fs/DirNode.cpp index 8b5ee16..57843aa 100644 --- a/fs/DirNode.cpp +++ b/fs/DirNode.cpp @@ -288,13 +288,7 @@ string DirNode::plainPath(const char *cipherPath_) { if (!strncmp(cipherPath_, rootDir.c_str(), rootDir.length())) { return naming->decodePath(cipherPath_ + rootDir.length()); } else { - if (cipherPath_[0] == '+') { - // decode as fully qualified path - return string("/") + - naming->decodeName(cipherPath_ + 1, strlen(cipherPath_ + 1)); - } else { - return naming->decodePath(cipherPath_); - } + return naming->decodePath(cipherPath_); } } catch (Error &err) { @@ -305,13 +299,7 @@ string DirNode::plainPath(const char *cipherPath_) { string DirNode::relativeCipherPath(const char *plaintextPath) { try { - if (plaintextPath[0] == '/') { - // mark with '+' to indicate special decoding.. - return string("+") + - naming->encodeName(plaintextPath + 1, strlen(plaintextPath + 1)); - } else { - return naming->encodePath(plaintextPath); - } + return naming->encodePath(plaintextPath); } catch (Error &err) { LOG(ERROR) << "encode err: " << err.what(); diff --git a/fs/test_IO.cpp b/fs/IO_test.cpp similarity index 100% rename from fs/test_IO.cpp rename to fs/IO_test.cpp diff --git a/fs/NameIO.cpp b/fs/NameIO.cpp index 4c2be73..b4c5605 100644 --- a/fs/NameIO.cpp +++ b/fs/NameIO.cpp @@ -24,27 +24,28 @@ #include -#include +#include #include +#include +#include // for static build. Need to reference the modules which are registered at // run-time, to ensure that the linker doesn't optimize them away. -#include #include "fs/BlockNameIO.h" #include "fs/StreamNameIO.h" #include "fs/NullNameIO.h" -using std::cerr; using std::list; using std::make_pair; using std::multimap; using std::string; +using std::vector; namespace encfs { -#define REF_MODULE(TYPE) \ - do { \ - if (!TYPE::Enabled()) cerr << "referenceModule: should never happen\n"; \ +#define REF_MODULE(TYPE) \ + do { \ + CHECK(TYPE::Enabled()) << "referenceModule: should never happen"; \ } while (0) static void AddSymbolReferences() { @@ -62,7 +63,7 @@ struct NameIOAlg { }; typedef multimap NameIOMap_t; -static NameIOMap_t *gNameIOMap = 0; +static NameIOMap_t *gNameIOMap = nullptr; list NameIO::GetAlgorithmList(bool includeHidden) { AddSymbolReferences(); @@ -145,136 +146,84 @@ void NameIO::setReverseEncryption(bool enable) { reverseEncryption = enable; } bool NameIO::getReverseEncryption() const { return reverseEncryption; } -std::string NameIO::recodePath(const char *path, - int (NameIO::*_length)(int) const, - int (NameIO::*_code)(const char *, int, - uint64_t *, char *) const, - uint64_t *iv) const { +string NameIO::recodePath(const string &path, int (NameIO::*_length)(int) const, + string (NameIO::*_code)(const string &, + uint64_t *) const, + uint64_t *iv) const { string output; - while (*path) { - if (*path == '/') { - if (!output.empty()) // don't start the string with '/' - output += '/'; - ++path; - } else { - bool isDotFile = (*path == '.'); - const char *next = strchr(path, '/'); - int len = next ? next - path : strlen(path); + for (auto it = path.begin(); it != path.end();) { + bool isDotFile = (*it == '.'); + auto next = std::find(it, path.end(), '/'); + int len = std::distance(it, next); - // at this point we know that len > 0 - if (isDotFile && (path[len - 1] == '.') && (len <= 2)) { + if (*it == '/') { + // don't start the string with '/' + output += output.empty() ? '+' : '/'; + len = 1; + } else if (*it == '+' && output.empty()) { + output += '/'; + len = 1; + } else if (isDotFile && (len <= 2) && (it[len - 1] == '.')) { output.append(len, '.'); // append [len] copies of '.' - path += len; - continue; - } - - // figure out buffer sizes + } else { int approxLen = (this->*_length)(len); if (approxLen <= 0) throw Error("Filename too small to decode"); - BUFFER_INIT(codeBuf, 32, (unsigned int)approxLen + 1); - // code the name - int codedLen = (this->*_code)(path, len, iv, codeBuf); - rAssert(codedLen <= approxLen); - rAssert(codeBuf[codedLen] == '\0'); - path += len; + string input(it, it + len); + string coded = (this->*_code)(input, iv); // append result to string - output += (char *)codeBuf; - - BUFFER_RESET(codeBuf); + output.append(coded); } + + it += len; } return output; } -std::string NameIO::encodePath(const char *plaintextPath) const { +string NameIO::encodePath(const string &plaintextPath) const { uint64_t iv = 0; return encodePath(plaintextPath, &iv); } -std::string NameIO::decodePath(const char *cipherPath) const { +string NameIO::decodePath(const string &cipherPath) const { uint64_t iv = 0; return decodePath(cipherPath, &iv); } -std::string NameIO::_encodePath(const char *plaintextPath, uint64_t *iv) const { +string NameIO::_encodePath(const string &plaintextPath, uint64_t *iv) const { // if chaining is not enabled, then the iv pointer is not used.. - if (!chainedNameIV) iv = 0; + if (!chainedNameIV) iv = nullptr; return recodePath(plaintextPath, &NameIO::maxEncodedNameLen, &NameIO::encodeName, iv); } -std::string NameIO::_decodePath(const char *cipherPath, uint64_t *iv) const { +string NameIO::_decodePath(const string &cipherPath, uint64_t *iv) const { // if chaining is not enabled, then the iv pointer is not used.. - if (!chainedNameIV) iv = 0; + if (!chainedNameIV) iv = nullptr; return recodePath(cipherPath, &NameIO::maxDecodedNameLen, &NameIO::decodeName, iv); } -std::string NameIO::encodePath(const char *path, uint64_t *iv) const { +string NameIO::encodePath(const string &path, uint64_t *iv) const { return getReverseEncryption() ? _decodePath(path, iv) : _encodePath(path, iv); } -std::string NameIO::decodePath(const char *path, uint64_t *iv) const { +string NameIO::decodePath(const string &path, uint64_t *iv) const { return getReverseEncryption() ? _encodePath(path, iv) : _decodePath(path, iv); } -int NameIO::encodeName(const char *input, int length, char *output) const { - return encodeName(input, length, (uint64_t *)0, output); +string NameIO::encodeName(const string &name) const { + return getReverseEncryption() ? decodeName(name, nullptr) + : encodeName(name, nullptr); } -int NameIO::decodeName(const char *input, int length, char *output) const { - return decodeName(input, length, (uint64_t *)0, output); -} - -std::string NameIO::_encodeName(const char *plaintextName, int length) const { - int approxLen = maxEncodedNameLen(length); - - BUFFER_INIT(codeBuf, 32, (unsigned int)approxLen + 1); - - // code the name - int codedLen = encodeName(plaintextName, length, 0, codeBuf); - rAssert(codedLen <= approxLen); - rAssert(codeBuf[codedLen] == '\0'); - - // append result to string - std::string result = (char *)codeBuf; - - BUFFER_RESET(codeBuf); - - return result; -} - -std::string NameIO::_decodeName(const char *encodedName, int length) const { - int approxLen = maxDecodedNameLen(length); - - BUFFER_INIT(codeBuf, 32, (unsigned int)approxLen + 1); - - // code the name - int codedLen = decodeName(encodedName, length, 0, codeBuf); - rAssert(codedLen <= approxLen); - rAssert(codeBuf[codedLen] == '\0'); - - // append result to string - std::string result = (char *)codeBuf; - - BUFFER_RESET(codeBuf); - - return result; -} - -std::string NameIO::encodeName(const char *path, int length) const { - return getReverseEncryption() ? _decodeName(path, length) - : _encodeName(path, length); -} - -std::string NameIO::decodeName(const char *path, int length) const { - return getReverseEncryption() ? _encodeName(path, length) - : _decodeName(path, length); +string NameIO::decodeName(const string &name) const { + return getReverseEncryption() ? encodeName(name, nullptr) + : decodeName(name, nullptr); } } // namespace encfs diff --git a/fs/NameIO.h b/fs/NameIO.h index 5f3e11c..ad20ad3 100644 --- a/fs/NameIO.h +++ b/fs/NameIO.h @@ -28,6 +28,7 @@ #include "base/Interface.h" #include "base/shared_ptr.h" +#include "base/types.h" namespace encfs { @@ -67,67 +68,39 @@ class NameIO { void setReverseEncryption(bool enable); bool getReverseEncryption() const; - std::string encodePath(const char *plaintextPath) const; - std::string decodePath(const char *encodedPath) const; + std::string encodePath(const std::string &plaintextPath) const; + std::string decodePath(const std::string &encodedPath) const; - std::string encodePath(const char *plaintextPath, uint64_t *iv) const; - std::string decodePath(const char *encodedPath, uint64_t *iv) const; + std::string encodePath(const std::string &plaintextPath, uint64_t *iv) const; + std::string decodePath(const std::string &encodedPath, uint64_t *iv) const; virtual int maxEncodedNameLen(int plaintextNameLen) const = 0; virtual int maxDecodedNameLen(int encodedNameLen) const = 0; - std::string encodeName(const char *plaintextName, int length) const; - std::string decodeName(const char *encodedName, int length) const; + std::string encodeName(const std::string &plaintextName) const; + std::string decodeName(const std::string &encodedName) const; protected: - virtual int encodeName(const char *plaintextName, int length, - char *encodedName) const; - virtual int decodeName(const char *encodedName, int length, - char *plaintextName) const; - - virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const = 0; - virtual int decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const = 0; + // Encode & decode methods implemented by derived classes. + virtual std::string encodeName(const std::string &name, + uint64_t *iv) const = 0; + virtual std::string decodeName(const std::string &name, + uint64_t *iv) const = 0; private: - std::string recodePath(const char *path, int (NameIO::*codingLen)(int) const, - int (NameIO::*codingFunc)(const char *, int, - uint64_t *, char *) const, + std::string recodePath(const std::string &path, + int (NameIO::*codingLen)(int) const, + std::string (NameIO::*codingFunc)(const std::string &, + uint64_t *) const, uint64_t *iv) const; - std::string _encodePath(const char *plaintextPath, uint64_t *iv) const; - std::string _decodePath(const char *encodedPath, uint64_t *iv) const; - std::string _encodeName(const char *plaintextName, int length) const; - std::string _decodeName(const char *encodedName, int length) const; + std::string _encodePath(const std::string &plaintextPath, uint64_t *iv) const; + std::string _decodePath(const std::string &encodedPath, uint64_t *iv) const; bool chainedNameIV; bool reverseEncryption; }; -/* - Helper macros for creating temporary buffers with an optimization that - below a given size (OptimizedSize) is allocated on the stack, and when a - larger size is requested it is allocated on the heap. - - BUFFER_RESET should be called for the same name as BUFFER_INIT - */ -#define BUFFER_INIT(Name, OptimizedSize, Size) \ - char Name##_Raw[OptimizedSize]; \ - char *Name = Name##_Raw; \ - if (sizeof(Name##_Raw) < Size) { \ - Name = new char[Size]; \ - } \ - memset(Name, 0, Size) - -#define BUFFER_RESET(Name) \ - do { \ - if (Name != Name##_Raw) { \ - delete[] Name; \ - Name = Name##_Raw; \ - } \ - } while (0) - } // namespace encfs #endif diff --git a/fs/NameIO_test.cpp b/fs/NameIO_test.cpp new file mode 100644 index 0000000..ae1a7ca --- /dev/null +++ b/fs/NameIO_test.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "cipher/CipherV1.h" + +#include "fs/BlockNameIO.h" +#include "fs/NameIO.h" +#include "fs/NullNameIO.h" +#include "fs/StreamNameIO.h" +#include "fs/testing.h" + +namespace { + +using namespace encfs; +using std::string; + +string TEST_PATHS[] = {"a/b/c/d/e", // + "/a/b", // + "/a", // + "/", // + "../../foo/bar", // + "./.encfs", // + "."}; + +TEST(NameIOTest, NameIO) { + NameIO::AlgorithmList algorithms = NameIO::GetAlgorithmList(true); + // Test all NameIO algorithms. + for (auto algorithm : algorithms) { + shared_ptr cipher = CipherV1::New("AES", 256); + CipherKey key = cipher->newRandomKey(); + cipher->setKey(key); + + // Test all supported versions. + for (unsigned int version = algorithm.iface.major() - algorithm.iface.age(); + version <= algorithm.iface.major(); ++version) { + Interface iface = makeInterface(algorithm.iface.name(), version, 0, 0); + SCOPED_TRACE(testing::Message() << "Testing " << iface.DebugString()); + + auto io = NameIO::New(iface, cipher); + + // Check round-trip of test paths. + for (string path : TEST_PATHS) { + string encoded = io->encodePath(path); + string decoded = io->decodePath(encoded); + ASSERT_EQ(path, decoded); + } + + // Try encoding names of various lengths. + for (int len = 1; len < 40; ++len) { + string name(len, 'A'); + string encoded = io->encodeName(name); + string decoded = io->decodeName(encoded); + ASSERT_EQ(name, decoded); + } + } + } +} + +} // namespace diff --git a/fs/NullNameIO.cpp b/fs/NullNameIO.cpp index 79cc9f1..b8b0100 100644 --- a/fs/NullNameIO.cpp +++ b/fs/NullNameIO.cpp @@ -23,9 +23,12 @@ #include "fs/NullNameIO.h" #include +#include namespace encfs { +using std::string; + static shared_ptr NewNNIO(const Interface &, const shared_ptr &) { return shared_ptr(new NullNameIO()); @@ -51,16 +54,12 @@ int NullNameIO::maxDecodedNameLen(int encodedNameLen) const { return encodedNameLen; } -int NullNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const { - memcpy(encodedName, plaintextName, length); - return length; +string NullNameIO::encodeName(const string &plaintextName, uint64_t *iv) const { + return plaintextName; } -int NullNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const { - memcpy(plaintextName, encodedName, length); - return length; +string NullNameIO::decodeName(const string &encodedName, uint64_t *iv) const { + return encodedName; } bool NullNameIO::Enabled() { return true; } diff --git a/fs/NullNameIO.h b/fs/NullNameIO.h index acdbb12..e3e43aa 100644 --- a/fs/NullNameIO.h +++ b/fs/NullNameIO.h @@ -33,19 +33,19 @@ class NullNameIO : public NameIO { virtual ~NullNameIO(); - virtual Interface interface() const; + virtual Interface interface() const override; - virtual int maxEncodedNameLen(int plaintextNameLen) const; - virtual int maxDecodedNameLen(int encodedNameLen) const; + virtual int maxEncodedNameLen(int plaintextNameLen) const override; + virtual int maxDecodedNameLen(int encodedNameLen) const override; // hack to help with static builds static bool Enabled(); protected: - virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const; - virtual int decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const; + virtual std::string encodeName(const std::string &plaintextName, + uint64_t *iv) const override; + virtual std::string decodeName(const std::string &encodedName, + uint64_t *iv) const override; private: }; diff --git a/fs/StreamNameIO.cpp b/fs/StreamNameIO.cpp index f92acc6..3b6e708 100644 --- a/fs/StreamNameIO.cpp +++ b/fs/StreamNameIO.cpp @@ -27,9 +27,14 @@ #include #include +#include +#include namespace encfs { +using std::string; +using std::vector; + static shared_ptr NewStreamNameIO(const Interface &iface, const shared_ptr &cipher) { return shared_ptr(new StreamNameIO(iface, cipher)); @@ -60,10 +65,12 @@ static bool StreamIO_registered = NameIO::Register( bits. - Version 2.1 adds support for version 0 for EncFS 0.x compatibility. + + - Version 3.0 drops Encfs 0.x support. */ Interface StreamNameIO::CurrentInterface() { - // implement major version 2, 1, and 0 - return makeInterface("nameio/stream", 2, 1, 2); + // implements support for version 3, 2, and 1. + return makeInterface("nameio/stream", 3, 0, 2); } StreamNameIO::StreamNameIO(const Interface &iface, @@ -84,92 +91,73 @@ int StreamNameIO::maxDecodedNameLen(int encodedStreamLen) const { return decLen256 - 2; } -int StreamNameIO::encodeName(const char *plaintextName, int length, - uint64_t *iv, char *encodedName) const { +string StreamNameIO::encodeName(const string &plaintextName, + uint64_t *iv) const { uint64_t tmpIV = 0; + int length = plaintextName.length(); if (iv && _interface >= 2) tmpIV = *iv; - unsigned int mac = _cipher->reduceMac16( - _cipher->MAC_64((const byte *)plaintextName, length, iv)); + unsigned int mac = _cipher->reduceMac16(_cipher->MAC_64( + reinterpret_cast(plaintextName.data()), length, iv)); + tmpIV ^= (uint64_t)mac; - // add on checksum bytes - unsigned char *encodeBegin; - if (_interface >= 1) { - // current versions store the checksum at the beginning - encodedName[0] = (mac >> 8) & 0xff; - encodedName[1] = (mac) & 0xff; - encodeBegin = (unsigned char *)encodedName + 2; - } else { - // encfs 0.x stored checksums at the end. - encodedName[length] = (mac >> 8) & 0xff; - encodedName[length + 1] = (mac) & 0xff; - encodeBegin = (unsigned char *)encodedName; - } - - // stream encode the plaintext bytes - memcpy(encodeBegin, plaintextName, length); - _cipher->streamEncode(encodeBegin, length, (uint64_t)mac ^ tmpIV); - - // convert the entire thing to base 64 ascii.. int encodedStreamLen = length + 2; int encLen64 = B256ToB64Bytes(encodedStreamLen); - changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6, true); - B64ToAscii((unsigned char *)encodedName, encLen64); + // add on checksum bytes + vector encoded(encLen64); + encoded[0] = static_cast((mac >> 8) & 0xff); + encoded[1] = static_cast((mac) & 0xff); - return encLen64; + // stream encode the plaintext bytes + memcpy(&encoded[2], plaintextName.data(), length); + _cipher->streamEncode(&encoded[2], length, tmpIV); + + // convert the entire thing to base 64 ascii.. + changeBase2Inline(encoded.data(), encodedStreamLen, 8, 6, true); + B64ToAscii(encoded.data(), encLen64); + + return string(encoded.begin(), encoded.end()); } -int StreamNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const { +string StreamNameIO::decodeName(const string &encodedName, + uint64_t *iv) const { + int length = encodedName.length(); rAssert(length > 2); int decLen256 = B64ToB256Bytes(length); int decodedStreamLen = decLen256 - 2; if (decodedStreamLen <= 0) throw Error("Filename too small to decode"); - BUFFER_INIT(tmpBuf, 32, (unsigned int)length); + vector tmpBuf(length, 0); // decode into tmpBuf, because this step produces more data then we can fit // into the result buffer.. - AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); - changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false); + memcpy(tmpBuf.data(), encodedName.data(), length); + AsciiToB64(tmpBuf.data(), length); + changeBase2Inline(tmpBuf.data(), length, 6, 8, false); // pull out the checksum value which is used as an initialization vector uint64_t tmpIV = 0; - unsigned int mac; - if (_interface >= 1) { - // current versions store the checksum at the beginning - mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8 | - ((unsigned int)((unsigned char)tmpBuf[1])); + unsigned int mac = ((unsigned int)tmpBuf[0]) << 8 | ((unsigned int)tmpBuf[1]); - // version 2 adds support for IV chaining.. - if (iv && _interface >= 2) tmpIV = *iv; + // version 2 adds support for IV chaining.. + if (iv && _interface >= 2) tmpIV = *iv; - memcpy(plaintextName, tmpBuf + 2, decodedStreamLen); - } else { - // encfs 0.x stored checksums at the end. - mac = ((unsigned int)((unsigned char)tmpBuf[decodedStreamLen])) << 8 | - ((unsigned int)((unsigned char)tmpBuf[decodedStreamLen + 1])); - - memcpy(plaintextName, tmpBuf, decodedStreamLen); - } - - _cipher->streamDecode((unsigned char *)plaintextName, decodedStreamLen, - (uint64_t)mac ^ tmpIV); + tmpIV ^= (uint64_t)mac; + _cipher->streamDecode(&tmpBuf.at(2), decodedStreamLen, tmpIV); // compute MAC to check with stored value unsigned int mac2 = _cipher->reduceMac16( - _cipher->MAC_64((const byte *)plaintextName, decodedStreamLen, iv)); + _cipher->MAC_64(&tmpBuf.at(2), decodedStreamLen, iv)); - BUFFER_RESET(tmpBuf); if (mac2 != mac) { VLOG(1) << "checksum mismatch: expected " << mac << ", got " << mac2 << "on decode of " << decodedStreamLen << " bytes"; throw Error("checksum mismatch in filename decode"); } - return decodedStreamLen; + return string(reinterpret_cast(&tmpBuf.at(2)), decodedStreamLen); } bool StreamNameIO::Enabled() { return true; } diff --git a/fs/StreamNameIO.h b/fs/StreamNameIO.h index 923ee1c..ccc4bc7 100644 --- a/fs/StreamNameIO.h +++ b/fs/StreamNameIO.h @@ -34,19 +34,19 @@ class StreamNameIO : public NameIO { StreamNameIO(const Interface &iface, const shared_ptr &cipher); virtual ~StreamNameIO(); - virtual Interface interface() const; + virtual Interface interface() const override; - virtual int maxEncodedNameLen(int plaintextNameLen) const; - virtual int maxDecodedNameLen(int encodedNameLen) const; + virtual int maxEncodedNameLen(int plaintextNameLen) const override; + virtual int maxDecodedNameLen(int encodedNameLen) const override; // hack to help with static builds static bool Enabled(); protected: - virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, - char *encodedName) const; - virtual int decodeName(const char *encodedName, int length, uint64_t *iv, - char *plaintextName) const; + virtual std::string encodeName(const std::string &plaintextName, + uint64_t *iv) const override; + virtual std::string decodeName(const std::string &encodedName, + uint64_t *iv) const override; private: int _interface; diff --git a/fs/testing.cpp b/fs/testing.cpp index a5a0aef..012d044 100644 --- a/fs/testing.cpp +++ b/fs/testing.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include "cipher/CipherV1.h" @@ -188,6 +189,8 @@ void comparisonTest(FSConfigPtr& cfg, FileIO* a, FileIO* b) { int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); + google::InitGoogleLogging(argv[0]); + return RUN_ALL_TESTS(); }