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
This commit is contained in:
Valient Gough 2013-10-21 05:38:27 +00:00
parent 88f041a0b2
commit d1a9ccdd19
18 changed files with 265 additions and 306 deletions

View File

@ -27,7 +27,10 @@
namespace encfs { 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() << ":" out << iface.name() << "(" << iface.major() << ":" << iface.minor() << ":"
<< iface.age() << ")"; << iface.age() << ")";
return out; return out;
@ -42,7 +45,7 @@ bool implements(const Interface &A, const Interface &B) {
return (currentDiff >= 0 && currentDiff <= (int)A.age()); 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; Interface iface;
iface.set_name(name); iface.set_name(name);
iface.set_major(major); iface.set_major(major);

View File

@ -32,7 +32,7 @@ namespace encfs {
// compatibility. Even if A.implements(B) is true, B > A may also be // 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. // true, meaning B is a newer revision of the interface then A.
bool implements(const Interface &a, const Interface &b); 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 // Read operation
class ConfigVar; class ConfigVar;

View File

@ -132,11 +132,9 @@ static const byte Ascii2B64Table[] =
" 01 23456789:; "; " 01 23456789:; ";
// 0123456789 123456789 123456789 123456789 123456789 123456789 1234 // 0123456789 123456789 123456789 123456789 123456789 123456789 1234
// 0 1 2 3 4 5 6 // 0 1 2 3 4 5 6
void AsciiToB64(byte *in, int length) { return AsciiToB64(in, in, length); } void AsciiToB64(byte *buf, int length) {
void AsciiToB64(byte *out, const byte *in, int length) {
while (length--) { while (length--) {
byte ch = *in++; byte ch = *buf;
if (ch >= 'A') { if (ch >= 'A') {
if (ch >= 'a') if (ch >= 'a')
ch += 38 - 'a'; ch += 38 - 'a';
@ -145,7 +143,7 @@ void AsciiToB64(byte *out, const byte *in, int length) {
} else } else
ch = Ascii2B64Table[ch] - '0'; 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 *buf, int length) {
void AsciiToB32(byte *out, const byte *in, int length) {
while (length--) { while (length--) {
byte ch = *in++; byte ch = *buf;
int lch = toupper(ch); int lch = toupper(ch);
if (lch >= 'A') if (lch >= 'A')
lch -= 'A'; lch -= 'A';
else else
lch += 26 - '2'; lch += 26 - '2';
*out++ = (byte)lch; *buf++ = (byte)lch;
} }
} }

View File

@ -54,19 +54,22 @@ void changeBase2Inline(byte *buf, int srcLength, int srcPow2, int dst2Pow,
bool outputPartialLastByte); bool outputPartialLastByte);
// inplace translation from values [0,2^6] => base64 ASCII // 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); void B64ToAscii(byte *buf, int length);
// inplace translation from values [0,2^5] => base32 ASCII // inplace translation from values [0,2^5] => base32 ASCII
void B32ToAscii(byte *buf, int length); void B32ToAscii(byte *buf, int length);
// inplace translation from values base64 ASCII => [0,2^6] // 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 *buf, int length);
void AsciiToB64(byte *out, const byte *in, int length);
// inplace translation from values base32 ASCII => [0,2^5] // inplace translation from values base32 ASCII => [0,2^5]
void AsciiToB32(byte *buf, int length); 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). // The output size must be at least B64ToB256Bytes(inputLen).
bool B64StandardDecode(byte *out, const byte *in, int inputLen); bool B64StandardDecode(byte *out, const byte *in, int inputLen);

View File

@ -26,10 +26,15 @@
#include "cipher/CipherV1.h" #include "cipher/CipherV1.h"
#include <cstring> #include <cstring>
#include <vector>
#include <glog/logging.h> #include <glog/logging.h>
namespace encfs { namespace encfs {
using std::string;
using std::vector;
static shared_ptr<NameIO> NewBlockNameIO(const Interface &iface, static shared_ptr<NameIO> NewBlockNameIO(const Interface &iface,
const shared_ptr<CipherV1> &cipher) { const shared_ptr<CipherV1> &cipher) {
return shared_ptr<NameIO>(new BlockNameIO(iface, cipher, false)); return shared_ptr<NameIO>(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. - 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 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 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). case-insensitive filesystems (eg Mac).
*/ */
Interface BlockNameIO::CurrentInterface(bool caseSensitive) { 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) if (caseSensitive)
return makeInterface("nameio/block32", 4, 0, 2); return makeInterface("nameio/block32", 4, 0, 2);
else else
@ -113,16 +118,20 @@ int BlockNameIO::maxDecodedNameLen(int encodedNameLen) const {
return decLen256 - 2; // 2 checksum bytes removed.. return decLen256 - 2; // 2 checksum bytes removed..
} }
int BlockNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv, string BlockNameIO::encodeName(const string &plaintextName,
char *encodedName) const { uint64_t *iv) const {
// copy the data into the encoding buffer.. int length = plaintextName.length();
memcpy(encodedName + 2, plaintextName, length);
// Pad encryption buffer to block boundary.. // Pad encryption buffer to block boundary..
int padding = _bs - length % _bs; 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<byte> 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. // store the IV before it is modified by the MAC call.
uint64_t tmpIV = 0; uint64_t tmpIV = 0;
@ -130,38 +139,29 @@ int BlockNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv,
// include padding in MAC computation // include padding in MAC computation
unsigned int mac = _cipher->reduceMac16( 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 // add checksum bytes
encodedName[0] = (mac >> 8) & 0xff; tmpBuf[0] = (mac >> 8) & 0xff;
encodedName[1] = (mac) & 0xff; tmpBuf[1] = (mac) & 0xff;
_cipher->blockEncode((unsigned char *)encodedName + 2, length + padding, _cipher->blockEncode(tmpBuf.data() + 2, length + padding, tmpIV);
(uint64_t)mac ^ tmpIV);
// convert to base 64 ascii
int encodedStreamLen = length + 2 + padding;
int encLen;
// convert to base 32 or 64 ascii
if (_caseSensitive) { if (_caseSensitive) {
encLen = B256ToB32Bytes(encodedStreamLen); changeBase2Inline(tmpBuf.data(), encodedStreamLen, 8, 5, true);
B32ToAscii(tmpBuf.data(), encLen);
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 5,
true);
B32ToAscii((unsigned char *)encodedName, encLen);
} else { } else {
encLen = B256ToB64Bytes(encodedStreamLen); changeBase2Inline(tmpBuf.data(), encodedStreamLen, 8, 6, true);
B64ToAscii(tmpBuf.data(), encLen);
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6,
true);
B64ToAscii((unsigned char *)encodedName, encLen);
} }
return encLen; return string(reinterpret_cast<char*>(tmpBuf.data()), encLen);
} }
int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, string BlockNameIO::decodeName(const string &encodedName, uint64_t *iv) const {
char *plaintextName) const { int length = encodedName.length();
int decLen256 = int decLen256 =
_caseSensitive ? B32ToB256Bytes(length) : B64ToB256Bytes(length); _caseSensitive ? B32ToB256Bytes(length) : B64ToB256Bytes(length);
int decodedStreamLen = decLen256 - 2; 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 // don't bother trying to decode files which are too small
if (decodedStreamLen < _bs) throw Error("Filename too small to decode"); if (decodedStreamLen < _bs) throw Error("Filename too small to decode");
BUFFER_INIT(tmpBuf, 32, (unsigned int)length); vector<byte> tmpBuf(length, 0);
memcpy(tmpBuf.data(), encodedName.data(), length);
// decode into tmpBuf, // decode into tmpBuf,
if (_caseSensitive) { if (_caseSensitive) {
AsciiToB32((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); AsciiToB32(tmpBuf.data(), length);
changeBase2Inline((unsigned char *)tmpBuf, length, 5, 8, false); changeBase2Inline(tmpBuf.data(), length, 5, 8, false);
} else { } else {
AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); AsciiToB64(tmpBuf.data(), length);
changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false); changeBase2Inline(tmpBuf.data(), length, 6, 8, false);
} }
// pull out the header information // pull out the header information
unsigned int mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8 | unsigned int mac = ((unsigned int)tmpBuf[0]) << 8 |
((unsigned int)((unsigned char)tmpBuf[1])); ((unsigned int)tmpBuf[1]);
uint64_t tmpIV = 0; uint64_t tmpIV = 0;
if (iv && _interface >= 3) tmpIV = *iv; if (iv && _interface >= 3) tmpIV = *iv;
tmpIV ^= (uint64_t)mac;
_cipher->blockDecode((unsigned char *)tmpBuf + 2, decodedStreamLen, _cipher->blockDecode(&tmpBuf.at(2), decodedStreamLen, tmpIV);
(uint64_t)mac ^ tmpIV);
// find out true string length // find out true string length
int padding = (unsigned char)tmpBuf[2 + decodedStreamLen - 1]; int padding = tmpBuf[2 + decodedStreamLen - 1];
int finalSize = decodedStreamLen - padding; int finalSize = decodedStreamLen - padding;
// might happen if there is an error decoding.. // 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"); throw Error("invalid padding size");
} }
// copy out the result..
memcpy(plaintextName, tmpBuf + 2, finalSize);
plaintextName[finalSize] = '\0';
// check the mac // check the mac
unsigned int mac2 = _cipher->reduceMac16( unsigned int mac2 = _cipher->reduceMac16(
_cipher->MAC_64((const unsigned char *)tmpBuf + 2, decodedStreamLen, iv)); _cipher->MAC_64(&tmpBuf.at(2), decodedStreamLen, iv));
BUFFER_RESET(tmpBuf);
if (mac2 != mac) { if (mac2 != mac) {
LOG(INFO) << "checksum mismatch: expected " << mac << ", got " << mac2 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"); throw Error("checksum mismatch in filename decode");
} }
return finalSize; return string(reinterpret_cast<char*>(&tmpBuf.at(2)), finalSize);
} }
bool BlockNameIO::Enabled() { return true; } bool BlockNameIO::Enabled() { return true; }

View File

@ -24,6 +24,7 @@
#include "fs/NameIO.h" #include "fs/NameIO.h"
#include <memory> #include <memory>
#include <vector>
namespace encfs { namespace encfs {
@ -42,19 +43,19 @@ class BlockNameIO : public NameIO {
bool caseSensitiveEncoding = false); bool caseSensitiveEncoding = false);
virtual ~BlockNameIO(); virtual ~BlockNameIO();
virtual Interface interface() const; virtual Interface interface() const override;
virtual int maxEncodedNameLen(int plaintextNameLen) const; virtual int maxEncodedNameLen(int plaintextNameLen) const override;
virtual int maxDecodedNameLen(int encodedNameLen) const; virtual int maxDecodedNameLen(int encodedNameLen) const override;
// hack to help with static builds // hack to help with static builds
static bool Enabled(); static bool Enabled();
protected: protected:
virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, virtual std::string encodeName(const std::string &plaintextName,
char *encodedName) const; uint64_t *iv) const override;
virtual int decodeName(const char *encodedName, int length, uint64_t *iv, virtual std::string decodeName(const std::string &encodedName,
char *plaintextName) const; uint64_t *iv) const override;
private: private:
int _interface; int _interface;

View File

@ -41,12 +41,14 @@ if (GTEST_FOUND)
link_directories (${PROJECT_BINARY_DIR}/cipher) link_directories (${PROJECT_BINARY_DIR}/cipher)
include_directories (${GTEST_INCLUDE_DIR}) include_directories (${GTEST_INCLUDE_DIR})
file (GLOB TEST_FILES "*_test.cpp")
add_executable (fs-tests add_executable (fs-tests
MemBlockFileIO.cpp MemBlockFileIO.cpp
MemFileIO.cpp MemFileIO.cpp
testing.cpp testing.cpp
test_IO.cpp ${TEST_FILES}
test_BlockIO.cpp
) )
target_link_libraries (fs-tests target_link_libraries (fs-tests
@ -58,5 +60,5 @@ if (GTEST_FOUND)
) )
add_test (FSTests fs-tests) 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) endif (GTEST_FOUND)

View File

@ -288,13 +288,7 @@ string DirNode::plainPath(const char *cipherPath_) {
if (!strncmp(cipherPath_, rootDir.c_str(), rootDir.length())) { if (!strncmp(cipherPath_, rootDir.c_str(), rootDir.length())) {
return naming->decodePath(cipherPath_ + rootDir.length()); return naming->decodePath(cipherPath_ + rootDir.length());
} else { } else {
if (cipherPath_[0] == '+') { return naming->decodePath(cipherPath_);
// decode as fully qualified path
return string("/") +
naming->decodeName(cipherPath_ + 1, strlen(cipherPath_ + 1));
} else {
return naming->decodePath(cipherPath_);
}
} }
} }
catch (Error &err) { catch (Error &err) {
@ -305,13 +299,7 @@ string DirNode::plainPath(const char *cipherPath_) {
string DirNode::relativeCipherPath(const char *plaintextPath) { string DirNode::relativeCipherPath(const char *plaintextPath) {
try { try {
if (plaintextPath[0] == '/') { return naming->encodePath(plaintextPath);
// mark with '+' to indicate special decoding..
return string("+") +
naming->encodeName(plaintextPath + 1, strlen(plaintextPath + 1));
} else {
return naming->encodePath(plaintextPath);
}
} }
catch (Error &err) { catch (Error &err) {
LOG(ERROR) << "encode err: " << err.what(); LOG(ERROR) << "encode err: " << err.what();

View File

@ -24,27 +24,28 @@
#include <glog/logging.h> #include <glog/logging.h>
#include <map> #include <algorithm>
#include <cstring> #include <cstring>
#include <map>
#include <vector>
// for static build. Need to reference the modules which are registered at // for static build. Need to reference the modules which are registered at
// run-time, to ensure that the linker doesn't optimize them away. // run-time, to ensure that the linker doesn't optimize them away.
#include <iostream>
#include "fs/BlockNameIO.h" #include "fs/BlockNameIO.h"
#include "fs/StreamNameIO.h" #include "fs/StreamNameIO.h"
#include "fs/NullNameIO.h" #include "fs/NullNameIO.h"
using std::cerr;
using std::list; using std::list;
using std::make_pair; using std::make_pair;
using std::multimap; using std::multimap;
using std::string; using std::string;
using std::vector;
namespace encfs { namespace encfs {
#define REF_MODULE(TYPE) \ #define REF_MODULE(TYPE) \
do { \ do { \
if (!TYPE::Enabled()) cerr << "referenceModule: should never happen\n"; \ CHECK(TYPE::Enabled()) << "referenceModule: should never happen"; \
} while (0) } while (0)
static void AddSymbolReferences() { static void AddSymbolReferences() {
@ -62,7 +63,7 @@ struct NameIOAlg {
}; };
typedef multimap<string, NameIOAlg> NameIOMap_t; typedef multimap<string, NameIOAlg> NameIOMap_t;
static NameIOMap_t *gNameIOMap = 0; static NameIOMap_t *gNameIOMap = nullptr;
list<NameIO::Algorithm> NameIO::GetAlgorithmList(bool includeHidden) { list<NameIO::Algorithm> NameIO::GetAlgorithmList(bool includeHidden) {
AddSymbolReferences(); AddSymbolReferences();
@ -145,136 +146,84 @@ void NameIO::setReverseEncryption(bool enable) { reverseEncryption = enable; }
bool NameIO::getReverseEncryption() const { return reverseEncryption; } bool NameIO::getReverseEncryption() const { return reverseEncryption; }
std::string NameIO::recodePath(const char *path, string NameIO::recodePath(const string &path, int (NameIO::*_length)(int) const,
int (NameIO::*_length)(int) const, string (NameIO::*_code)(const string &,
int (NameIO::*_code)(const char *, int, uint64_t *) const,
uint64_t *, char *) const, uint64_t *iv) const {
uint64_t *iv) const {
string output; string output;
while (*path) { for (auto it = path.begin(); it != path.end();) {
if (*path == '/') { bool isDotFile = (*it == '.');
if (!output.empty()) // don't start the string with '/' auto next = std::find(it, path.end(), '/');
output += '/'; int len = std::distance(it, next);
++path;
} else {
bool isDotFile = (*path == '.');
const char *next = strchr(path, '/');
int len = next ? next - path : strlen(path);
// at this point we know that len > 0 if (*it == '/') {
if (isDotFile && (path[len - 1] == '.') && (len <= 2)) { // 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 '.' output.append(len, '.'); // append [len] copies of '.'
path += len; } else {
continue;
}
// figure out buffer sizes
int approxLen = (this->*_length)(len); int approxLen = (this->*_length)(len);
if (approxLen <= 0) throw Error("Filename too small to decode"); if (approxLen <= 0) throw Error("Filename too small to decode");
BUFFER_INIT(codeBuf, 32, (unsigned int)approxLen + 1);
// code the name // code the name
int codedLen = (this->*_code)(path, len, iv, codeBuf); string input(it, it + len);
rAssert(codedLen <= approxLen); string coded = (this->*_code)(input, iv);
rAssert(codeBuf[codedLen] == '\0');
path += len;
// append result to string // append result to string
output += (char *)codeBuf; output.append(coded);
BUFFER_RESET(codeBuf);
} }
it += len;
} }
return output; return output;
} }
std::string NameIO::encodePath(const char *plaintextPath) const { string NameIO::encodePath(const string &plaintextPath) const {
uint64_t iv = 0; uint64_t iv = 0;
return encodePath(plaintextPath, &iv); return encodePath(plaintextPath, &iv);
} }
std::string NameIO::decodePath(const char *cipherPath) const { string NameIO::decodePath(const string &cipherPath) const {
uint64_t iv = 0; uint64_t iv = 0;
return decodePath(cipherPath, &iv); 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 chaining is not enabled, then the iv pointer is not used..
if (!chainedNameIV) iv = 0; if (!chainedNameIV) iv = nullptr;
return recodePath(plaintextPath, &NameIO::maxEncodedNameLen, return recodePath(plaintextPath, &NameIO::maxEncodedNameLen,
&NameIO::encodeName, iv); &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 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, return recodePath(cipherPath, &NameIO::maxDecodedNameLen, &NameIO::decodeName,
iv); 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); 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); return getReverseEncryption() ? _encodePath(path, iv) : _decodePath(path, iv);
} }
int NameIO::encodeName(const char *input, int length, char *output) const { string NameIO::encodeName(const string &name) const {
return encodeName(input, length, (uint64_t *)0, output); return getReverseEncryption() ? decodeName(name, nullptr)
: encodeName(name, nullptr);
} }
int NameIO::decodeName(const char *input, int length, char *output) const { string NameIO::decodeName(const string &name) const {
return decodeName(input, length, (uint64_t *)0, output); return getReverseEncryption() ? encodeName(name, nullptr)
} : decodeName(name, nullptr);
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);
} }
} // namespace encfs } // namespace encfs

View File

@ -28,6 +28,7 @@
#include "base/Interface.h" #include "base/Interface.h"
#include "base/shared_ptr.h" #include "base/shared_ptr.h"
#include "base/types.h"
namespace encfs { namespace encfs {
@ -67,67 +68,39 @@ class NameIO {
void setReverseEncryption(bool enable); void setReverseEncryption(bool enable);
bool getReverseEncryption() const; bool getReverseEncryption() const;
std::string encodePath(const char *plaintextPath) const; std::string encodePath(const std::string &plaintextPath) const;
std::string decodePath(const char *encodedPath) const; std::string decodePath(const std::string &encodedPath) const;
std::string encodePath(const char *plaintextPath, uint64_t *iv) const; std::string encodePath(const std::string &plaintextPath, uint64_t *iv) const;
std::string decodePath(const char *encodedPath, uint64_t *iv) const; std::string decodePath(const std::string &encodedPath, uint64_t *iv) const;
virtual int maxEncodedNameLen(int plaintextNameLen) const = 0; virtual int maxEncodedNameLen(int plaintextNameLen) const = 0;
virtual int maxDecodedNameLen(int encodedNameLen) const = 0; virtual int maxDecodedNameLen(int encodedNameLen) const = 0;
std::string encodeName(const char *plaintextName, int length) const; std::string encodeName(const std::string &plaintextName) const;
std::string decodeName(const char *encodedName, int length) const; std::string decodeName(const std::string &encodedName) const;
protected: protected:
virtual int encodeName(const char *plaintextName, int length, // Encode & decode methods implemented by derived classes.
char *encodedName) const; virtual std::string encodeName(const std::string &name,
virtual int decodeName(const char *encodedName, int length, uint64_t *iv) const = 0;
char *plaintextName) const; virtual std::string decodeName(const std::string &name,
uint64_t *iv) const = 0;
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;
private: private:
std::string recodePath(const char *path, int (NameIO::*codingLen)(int) const, std::string recodePath(const std::string &path,
int (NameIO::*codingFunc)(const char *, int, int (NameIO::*codingLen)(int) const,
uint64_t *, char *) const, std::string (NameIO::*codingFunc)(const std::string &,
uint64_t *) const,
uint64_t *iv) const; uint64_t *iv) const;
std::string _encodePath(const char *plaintextPath, uint64_t *iv) const; std::string _encodePath(const std::string &plaintextPath, uint64_t *iv) const;
std::string _decodePath(const char *encodedPath, uint64_t *iv) const; std::string _decodePath(const std::string &encodedPath, uint64_t *iv) const;
std::string _encodeName(const char *plaintextName, int length) const;
std::string _decodeName(const char *encodedName, int length) const;
bool chainedNameIV; bool chainedNameIV;
bool reverseEncryption; 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 } // namespace encfs
#endif #endif

59
fs/NameIO_test.cpp Normal file
View File

@ -0,0 +1,59 @@
#include <gtest/gtest.h>
#include <string>
#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<CipherV1> 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

View File

@ -23,9 +23,12 @@
#include "fs/NullNameIO.h" #include "fs/NullNameIO.h"
#include <cstring> #include <cstring>
#include <string>
namespace encfs { namespace encfs {
using std::string;
static shared_ptr<NameIO> NewNNIO(const Interface &, static shared_ptr<NameIO> NewNNIO(const Interface &,
const shared_ptr<CipherV1> &) { const shared_ptr<CipherV1> &) {
return shared_ptr<NameIO>(new NullNameIO()); return shared_ptr<NameIO>(new NullNameIO());
@ -51,16 +54,12 @@ int NullNameIO::maxDecodedNameLen(int encodedNameLen) const {
return encodedNameLen; return encodedNameLen;
} }
int NullNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv, string NullNameIO::encodeName(const string &plaintextName, uint64_t *iv) const {
char *encodedName) const { return plaintextName;
memcpy(encodedName, plaintextName, length);
return length;
} }
int NullNameIO::decodeName(const char *encodedName, int length, uint64_t *iv, string NullNameIO::decodeName(const string &encodedName, uint64_t *iv) const {
char *plaintextName) const { return encodedName;
memcpy(plaintextName, encodedName, length);
return length;
} }
bool NullNameIO::Enabled() { return true; } bool NullNameIO::Enabled() { return true; }

View File

@ -33,19 +33,19 @@ class NullNameIO : public NameIO {
virtual ~NullNameIO(); virtual ~NullNameIO();
virtual Interface interface() const; virtual Interface interface() const override;
virtual int maxEncodedNameLen(int plaintextNameLen) const; virtual int maxEncodedNameLen(int plaintextNameLen) const override;
virtual int maxDecodedNameLen(int encodedNameLen) const; virtual int maxDecodedNameLen(int encodedNameLen) const override;
// hack to help with static builds // hack to help with static builds
static bool Enabled(); static bool Enabled();
protected: protected:
virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, virtual std::string encodeName(const std::string &plaintextName,
char *encodedName) const; uint64_t *iv) const override;
virtual int decodeName(const char *encodedName, int length, uint64_t *iv, virtual std::string decodeName(const std::string &encodedName,
char *plaintextName) const; uint64_t *iv) const override;
private: private:
}; };

View File

@ -27,9 +27,14 @@
#include <glog/logging.h> #include <glog/logging.h>
#include <cstring> #include <cstring>
#include <string>
#include <vector>
namespace encfs { namespace encfs {
using std::string;
using std::vector;
static shared_ptr<NameIO> NewStreamNameIO(const Interface &iface, static shared_ptr<NameIO> NewStreamNameIO(const Interface &iface,
const shared_ptr<CipherV1> &cipher) { const shared_ptr<CipherV1> &cipher) {
return shared_ptr<NameIO>(new StreamNameIO(iface, cipher)); return shared_ptr<NameIO>(new StreamNameIO(iface, cipher));
@ -60,10 +65,12 @@ static bool StreamIO_registered = NameIO::Register(
bits. bits.
- Version 2.1 adds support for version 0 for EncFS 0.x compatibility. - Version 2.1 adds support for version 0 for EncFS 0.x compatibility.
- Version 3.0 drops Encfs 0.x support.
*/ */
Interface StreamNameIO::CurrentInterface() { Interface StreamNameIO::CurrentInterface() {
// implement major version 2, 1, and 0 // implements support for version 3, 2, and 1.
return makeInterface("nameio/stream", 2, 1, 2); return makeInterface("nameio/stream", 3, 0, 2);
} }
StreamNameIO::StreamNameIO(const Interface &iface, StreamNameIO::StreamNameIO(const Interface &iface,
@ -84,92 +91,73 @@ int StreamNameIO::maxDecodedNameLen(int encodedStreamLen) const {
return decLen256 - 2; return decLen256 - 2;
} }
int StreamNameIO::encodeName(const char *plaintextName, int length, string StreamNameIO::encodeName(const string &plaintextName,
uint64_t *iv, char *encodedName) const { uint64_t *iv) const {
uint64_t tmpIV = 0; uint64_t tmpIV = 0;
int length = plaintextName.length();
if (iv && _interface >= 2) tmpIV = *iv; if (iv && _interface >= 2) tmpIV = *iv;
unsigned int mac = _cipher->reduceMac16( unsigned int mac = _cipher->reduceMac16(_cipher->MAC_64(
_cipher->MAC_64((const byte *)plaintextName, length, iv)); reinterpret_cast<const byte *>(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 encodedStreamLen = length + 2;
int encLen64 = B256ToB64Bytes(encodedStreamLen); int encLen64 = B256ToB64Bytes(encodedStreamLen);
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6, true); // add on checksum bytes
B64ToAscii((unsigned char *)encodedName, encLen64); vector<byte> encoded(encLen64);
encoded[0] = static_cast<byte>((mac >> 8) & 0xff);
encoded[1] = static_cast<byte>((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, string StreamNameIO::decodeName(const string &encodedName,
char *plaintextName) const { uint64_t *iv) const {
int length = encodedName.length();
rAssert(length > 2); rAssert(length > 2);
int decLen256 = B64ToB256Bytes(length); int decLen256 = B64ToB256Bytes(length);
int decodedStreamLen = decLen256 - 2; int decodedStreamLen = decLen256 - 2;
if (decodedStreamLen <= 0) throw Error("Filename too small to decode"); if (decodedStreamLen <= 0) throw Error("Filename too small to decode");
BUFFER_INIT(tmpBuf, 32, (unsigned int)length); vector<byte> tmpBuf(length, 0);
// decode into tmpBuf, because this step produces more data then we can fit // decode into tmpBuf, because this step produces more data then we can fit
// into the result buffer.. // into the result buffer..
AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length); memcpy(tmpBuf.data(), encodedName.data(), length);
changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false); AsciiToB64(tmpBuf.data(), length);
changeBase2Inline(tmpBuf.data(), length, 6, 8, false);
// pull out the checksum value which is used as an initialization vector // pull out the checksum value which is used as an initialization vector
uint64_t tmpIV = 0; uint64_t tmpIV = 0;
unsigned int mac; unsigned int mac = ((unsigned int)tmpBuf[0]) << 8 | ((unsigned int)tmpBuf[1]);
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]));
// version 2 adds support for IV chaining.. // version 2 adds support for IV chaining..
if (iv && _interface >= 2) tmpIV = *iv; if (iv && _interface >= 2) tmpIV = *iv;
memcpy(plaintextName, tmpBuf + 2, decodedStreamLen); tmpIV ^= (uint64_t)mac;
} else { _cipher->streamDecode(&tmpBuf.at(2), decodedStreamLen, tmpIV);
// 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);
// compute MAC to check with stored value // compute MAC to check with stored value
unsigned int mac2 = _cipher->reduceMac16( 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) { if (mac2 != mac) {
VLOG(1) << "checksum mismatch: expected " << mac << ", got " << mac2 VLOG(1) << "checksum mismatch: expected " << mac << ", got " << mac2
<< "on decode of " << decodedStreamLen << " bytes"; << "on decode of " << decodedStreamLen << " bytes";
throw Error("checksum mismatch in filename decode"); throw Error("checksum mismatch in filename decode");
} }
return decodedStreamLen; return string(reinterpret_cast<char*>(&tmpBuf.at(2)), decodedStreamLen);
} }
bool StreamNameIO::Enabled() { return true; } bool StreamNameIO::Enabled() { return true; }

View File

@ -34,19 +34,19 @@ class StreamNameIO : public NameIO {
StreamNameIO(const Interface &iface, const shared_ptr<CipherV1> &cipher); StreamNameIO(const Interface &iface, const shared_ptr<CipherV1> &cipher);
virtual ~StreamNameIO(); virtual ~StreamNameIO();
virtual Interface interface() const; virtual Interface interface() const override;
virtual int maxEncodedNameLen(int plaintextNameLen) const; virtual int maxEncodedNameLen(int plaintextNameLen) const override;
virtual int maxDecodedNameLen(int encodedNameLen) const; virtual int maxDecodedNameLen(int encodedNameLen) const override;
// hack to help with static builds // hack to help with static builds
static bool Enabled(); static bool Enabled();
protected: protected:
virtual int encodeName(const char *plaintextName, int length, uint64_t *iv, virtual std::string encodeName(const std::string &plaintextName,
char *encodedName) const; uint64_t *iv) const override;
virtual int decodeName(const char *encodedName, int length, uint64_t *iv, virtual std::string decodeName(const std::string &encodedName,
char *plaintextName) const; uint64_t *iv) const override;
private: private:
int _interface; int _interface;

View File

@ -24,6 +24,7 @@
#include <list> #include <list>
#include <string> #include <string>
#include <glog/logging.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "cipher/CipherV1.h" #include "cipher/CipherV1.h"
@ -188,6 +189,8 @@ void comparisonTest(FSConfigPtr& cfg, FileIO* a, FileIO* b) {
int main(int argc, char** argv) { int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }