mirror of
https://github.com/vgough/encfs.git
synced 2025-01-01 03:28:57 +01:00
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:
parent
88f041a0b2
commit
d1a9ccdd19
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -26,10 +26,15 @@
|
||||
#include "cipher/CipherV1.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
namespace encfs {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
static shared_ptr<NameIO> NewBlockNameIO(const Interface &iface,
|
||||
const shared_ptr<CipherV1> &cipher) {
|
||||
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.
|
||||
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<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.
|
||||
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<char*>(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<byte> 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<char*>(&tmpBuf.at(2)), finalSize);
|
||||
}
|
||||
|
||||
bool BlockNameIO::Enabled() { return true; }
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "fs/NameIO.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
139
fs/NameIO.cpp
139
fs/NameIO.cpp
@ -24,27 +24,28 @@
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
// 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 <iostream>
|
||||
#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<string, NameIOAlg> NameIOMap_t;
|
||||
static NameIOMap_t *gNameIOMap = 0;
|
||||
static NameIOMap_t *gNameIOMap = nullptr;
|
||||
|
||||
list<NameIO::Algorithm> 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
|
||||
|
63
fs/NameIO.h
63
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
|
||||
|
59
fs/NameIO_test.cpp
Normal file
59
fs/NameIO_test.cpp
Normal 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
|
@ -23,9 +23,12 @@
|
||||
#include "fs/NullNameIO.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace encfs {
|
||||
|
||||
using std::string;
|
||||
|
||||
static shared_ptr<NameIO> NewNNIO(const Interface &,
|
||||
const shared_ptr<CipherV1> &) {
|
||||
return shared_ptr<NameIO>(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; }
|
||||
|
@ -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:
|
||||
};
|
||||
|
@ -27,9 +27,14 @@
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace encfs {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
static shared_ptr<NameIO> NewStreamNameIO(const Interface &iface,
|
||||
const shared_ptr<CipherV1> &cipher) {
|
||||
return shared_ptr<NameIO>(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<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 encLen64 = B256ToB64Bytes(encodedStreamLen);
|
||||
|
||||
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6, true);
|
||||
B64ToAscii((unsigned char *)encodedName, encLen64);
|
||||
// add on checksum bytes
|
||||
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,
|
||||
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<byte> 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<char*>(&tmpBuf.at(2)), decodedStreamLen);
|
||||
}
|
||||
|
||||
bool StreamNameIO::Enabled() { return true; }
|
||||
|
@ -34,19 +34,19 @@ class StreamNameIO : public NameIO {
|
||||
StreamNameIO(const Interface &iface, const shared_ptr<CipherV1> &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;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user