/***************************************************************************** * Author: Valient Gough * ***************************************************************************** * 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 . */ #include "fs/BlockNameIO.h" #include "base/base64.h" #include "base/Error.h" #include "base/i18n.h" #include "cipher/CipherV1.h" #include #include namespace encfs { static shared_ptr NewBlockNameIO( const Interface &iface, const shared_ptr &cipher ) { return shared_ptr( new BlockNameIO( iface, cipher, false)); } static shared_ptr NewBlockNameIO32( const Interface &iface, const shared_ptr &cipher ) { return shared_ptr( new BlockNameIO( iface, cipher, 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, false); static bool BlockIO32_registered = NameIO::Register("Block32", // description of block name encoding algorithm.. // xgroup(setup) gettext_noop("Block encoding with base32 output for case-sensitive systems"), BlockNameIO::CurrentInterface(true), NewBlockNameIO32, false); /* - 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 caseSensitive) { // implement major version 4 plus support for two prior versions if (caseSensitive) return makeInterface("nameio/block32", 4, 0, 2); else return makeInterface("nameio/block", 4, 0, 2); } BlockNameIO::BlockNameIO( const Interface &iface, const shared_ptr &cipher, bool caseSensitiveEncoding ) : _interface( iface.major() ) , _bs( cipher->cipherBlockSize() ) , _cipher( cipher ) , _caseSensitive( caseSensitiveEncoding ) { rAssert( _bs < 128 ); } BlockNameIO::~BlockNameIO() { } Interface BlockNameIO::interface() const { return CurrentInterface(_caseSensitive); } 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 (_caseSensitive) return B256ToB32Bytes( encodedNameLen ); else return B256ToB64Bytes( encodedNameLen ); } int BlockNameIO::maxDecodedNameLen( int encodedNameLen ) const { int decLen256 = _caseSensitive ? B32ToB256Bytes( encodedNameLen ) : B64ToB256Bytes( encodedNameLen ); 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 ); // Pad encryption buffer to block boundary.. int padding = _bs - length % _bs; if(padding == 0) padding = _bs; // padding a full extra block! memset( encodedName+length+2, (unsigned char)padding, padding ); // store the IV before it is modified by the MAC call. uint64_t tmpIV = 0; if( iv && _interface >= 3 ) tmpIV = *iv; // include padding in MAC computation unsigned int mac = _cipher->reduceMac16( _cipher->MAC_64( (unsigned char *)encodedName+2, length+padding, iv )); // add checksum bytes encodedName[0] = (mac >> 8) & 0xff; encodedName[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; if (_caseSensitive) { 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 ) const { int decLen256 = _caseSensitive ? B32ToB256Bytes( length ) : B64ToB256Bytes( length ); int decodedStreamLen = decLen256 - 2; // 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 ); // decode into tmpBuf, if (_caseSensitive) { 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 && _interface >= 3 ) tmpIV = *iv; _cipher->blockDecode( (unsigned char *)tmpBuf+2, decodedStreamLen, (uint64_t)mac ^ tmpIV ); // 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.. 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 ); if(mac2 != mac) { LOG(INFO) << "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