encfs/encfs/BlockNameIO.cpp
2017-08-25 22:34:33 -07:00

267 lines
8.6 KiB
C++

/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004-2011, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BlockNameIO.h"
#include <cstring>
#include <memory>
#include <utility>
#include "Cipher.h"
#include "CipherKey.h"
#include "Error.h"
#include "Interface.h"
#include "NameIO.h"
#include "base64.h"
#include "easylogging++.h"
#include "intl/gettext.h"
namespace encfs {
static std::shared_ptr<NameIO> NewBlockNameIO(
const Interface &iface, const std::shared_ptr<Cipher> &cipher,
const CipherKey &key) {
int blockSize = 8;
if (cipher) {
blockSize = cipher->cipherBlockSize();
}
return std::shared_ptr<NameIO>(
new BlockNameIO(iface, cipher, key, blockSize, false));
}
static std::shared_ptr<NameIO> NewBlockNameIO32(
const Interface &iface, const std::shared_ptr<Cipher> &cipher,
const CipherKey &key) {
int blockSize = 8;
if (cipher) {
blockSize = cipher->cipherBlockSize();
}
return std::shared_ptr<NameIO>(
new BlockNameIO(iface, cipher, key, blockSize, true));
}
static bool BlockIO_registered = NameIO::Register(
"Block",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop("Block encoding, hides file name size somewhat"),
BlockNameIO::CurrentInterface(false), NewBlockNameIO);
static bool BlockIO32_registered = NameIO::Register(
"Block32",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop(
"Block encoding with base32 output for case-insensitive systems"),
BlockNameIO::CurrentInterface(true), NewBlockNameIO32);
/*
- Version 1.0 computed MAC over the filename, but not the padding bytes.
This version was from pre-release 1.1, never publically released, so no
backward compatibility necessary.
- Version 2.0 includes padding bytes in MAC computation. This way the MAC
computation uses the same number of bytes regardless of the number of
padding bytes.
- 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..
- Version 4.0 adds support for base32, creating names more suitable for
case-insensitive filesystems (eg Mac).
*/
Interface BlockNameIO::CurrentInterface(bool caseInsensitive) {
// implement major version 4 plus support for two prior versions
if (caseInsensitive) {
return Interface("nameio/block32", 4, 0, 2);
}
return Interface("nameio/block", 4, 0, 2);
}
BlockNameIO::BlockNameIO(const Interface &iface, std::shared_ptr<Cipher> cipher,
CipherKey key, int blockSize,
bool caseInsensitiveEncoding)
: _interface(iface.current()),
_bs(blockSize),
_cipher(std::move(cipher)),
_key(std::move(key)),
_caseInsensitive(caseInsensitiveEncoding) {
// just to be safe..
rAssert(blockSize < 128);
}
BlockNameIO::~BlockNameIO() = default;
Interface BlockNameIO::interface() const {
return CurrentInterface(_caseInsensitive);
}
int BlockNameIO::maxEncodedNameLen(int plaintextNameLen) const {
// number of blocks, rounded up.. Only an estimate at this point, err on
// the size of too much space rather then too little.
int numBlocks = (plaintextNameLen + _bs) / _bs;
int encodedNameLen = numBlocks * _bs + 2; // 2 checksum bytes
if (_caseInsensitive) {
return B256ToB32Bytes(encodedNameLen);
}
return B256ToB64Bytes(encodedNameLen);
}
int BlockNameIO::maxDecodedNameLen(int encodedNameLen) const {
int decLen256 = _caseInsensitive ? B32ToB256Bytes(encodedNameLen)
: B64ToB256Bytes(encodedNameLen);
return decLen256 - 2; // 2 checksum bytes removed..
}
int BlockNameIO::encodeName(const char *plaintextName, int length, uint64_t *iv,
char *encodedName, int bufferLength) const {
// Pad encryption buffer to block boundary..
int padding = _bs - length % _bs;
if (padding == 0) {
padding = _bs; // padding a full extra block!
}
rAssert(bufferLength >= length + 2 + padding);
memset(encodedName + length + 2, (unsigned char)padding, padding);
// copy the data into the encoding buffer..
memcpy(encodedName + 2, plaintextName, length);
// store the IV before it is modified by the MAC call.
uint64_t tmpIV = 0;
if ((iv != nullptr) && _interface >= 3) {
tmpIV = *iv;
}
// include padding in MAC computation
unsigned int mac = _cipher->MAC_16((unsigned char *)encodedName + 2,
length + padding, _key, iv);
// add checksum bytes
encodedName[0] = (mac >> 8) & 0xff;
encodedName[1] = (mac)&0xff;
bool ok;
ok = _cipher->blockEncode((unsigned char *)encodedName + 2, length + padding,
(uint64_t)mac ^ tmpIV, _key);
if (!ok) {
throw Error("block encode failed in filename encode");
}
// convert to base 64 ascii
int encodedStreamLen = length + 2 + padding;
int encLen;
if (_caseInsensitive) {
encLen = B256ToB32Bytes(encodedStreamLen);
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 5,
true);
B32ToAscii((unsigned char *)encodedName, encLen);
} else {
encLen = B256ToB64Bytes(encodedStreamLen);
changeBase2Inline((unsigned char *)encodedName, encodedStreamLen, 8, 6,
true);
B64ToAscii((unsigned char *)encodedName, encLen);
}
return encLen;
}
int BlockNameIO::decodeName(const char *encodedName, int length, uint64_t *iv,
char *plaintextName, int bufferLength) const {
int decLen256 =
_caseInsensitive ? B32ToB256Bytes(length) : B64ToB256Bytes(length);
int decodedStreamLen = decLen256 - 2;
// don't bother trying to decode files which are too small
if (decodedStreamLen < _bs) {
VLOG(1) << "Rejecting filename " << encodedName;
throw Error("Filename too small to decode");
}
BUFFER_INIT(tmpBuf, 32, (unsigned int)length);
// decode into tmpBuf,
if (_caseInsensitive) {
AsciiToB32((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, length, 5, 8, false);
} else {
AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, 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]));
uint64_t tmpIV = 0;
if ((iv != nullptr) && _interface >= 3) {
tmpIV = *iv;
}
bool ok;
ok = _cipher->blockDecode((unsigned char *)tmpBuf + 2, decodedStreamLen,
(uint64_t)mac ^ tmpIV, _key);
if (!ok) {
throw Error("block decode failed in filename decode");
}
// find out true string length
int padding = (unsigned char)tmpBuf[2 + decodedStreamLen - 1];
int finalSize = decodedStreamLen - padding;
// might happen if there is an error decoding..
if (padding > _bs || finalSize < 0) {
VLOG(1) << "padding, _bx, finalSize = " << padding << ", " << _bs << ", "
<< finalSize;
throw Error("invalid padding size");
}
// copy out the result..
rAssert(finalSize < bufferLength);
memcpy(plaintextName, tmpBuf + 2, finalSize);
plaintextName[finalSize] = '\0';
// check the mac
unsigned int mac2 = _cipher->MAC_16((const unsigned char *)tmpBuf + 2,
decodedStreamLen, _key, iv);
BUFFER_RESET(tmpBuf);
if (mac2 != mac) {
VLOG(1) << "checksum mismatch: expected " << mac << ", got " << mac2
<< " on decode of " << finalSize << " bytes";
throw Error("checksum mismatch in filename decode");
}
return finalSize;
}
bool BlockNameIO::Enabled() { return true; }
} // namespace encfs