/***************************************************************************** * Author: Valient Gough * ***************************************************************************** * Copyright (c) 2004-2013, 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/CipherFileIO.h" #include "base/Error.h" #include "cipher/CipherV1.h" #include "cipher/MemoryPool.h" #include "fs/fsconfig.pb.h" #include #include #include namespace encfs { /* Version 2:0 adds support for a per-file initialization vector with a fixed 8 byte header. The headers are enabled globally within a filesystem at the filesystem configuration level. When headers are disabled, 2:0 is compatible with version 1:0. */ static Interface CipherFileIO_iface = makeInterface("FileIO/Cipher", 3, 0, 2); CipherFileIO::CipherFileIO( const shared_ptr &_base, const FSConfigPtr &cfg) : BlockFileIO( cfg->config->block_size(), cfg ) , base( _base ) , headerLen( 0 ) , perFileIV( cfg->config->unique_iv() ) , externalIV( 0 ) , fileIV( 0 ) , lastFlags( 0 ) { fsConfig = cfg; cipher = cfg->cipher; if ( perFileIV ) headerLen += sizeof(uint64_t); // 64bit IV per file int blockBoundary = fsConfig->config->block_size() % fsConfig->cipher->cipherBlockSize(); if(blockBoundary != 0) { LOG_FIRST_N(ERROR, 1) << "CipherFileIO: blocks should be multiple of cipher block size"; } } CipherFileIO::~CipherFileIO() { } Interface CipherFileIO::interface() const { return CipherFileIO_iface; } int CipherFileIO::open( int flags ) { int res = base->open( flags ); if( res >= 0 ) lastFlags = flags; return res; } void CipherFileIO::setFileName( const char *fileName ) { base->setFileName( fileName ); } const char *CipherFileIO::getFileName() const { return base->getFileName(); } bool CipherFileIO::setIV( uint64_t iv ) { VLOG(1) << "in setIV, current IV = " << externalIV << ", new IV = " << iv << ", fileIV = " << fileIV; if(externalIV == 0) { // we're just being told about which IV to use. since we haven't // initialized the fileIV, there is no need to just yet.. externalIV = iv; LOG_IF(WARNING, fileIV != 0) << "fileIV initialized before externalIV! (" << fileIV << ", " << externalIV << ")"; } else if(perFileIV) { // we have an old IV, and now a new IV, so we need to update the fileIV // on disk. if(fileIV == 0) { // ensure the file is open for read/write.. int newFlags = lastFlags | O_RDWR; int res = base->open( newFlags ); if(res < 0) { if(res == -EISDIR) { // duh -- there are no file headers for directories! externalIV = iv; return base->setIV( iv ); } else { VLOG(1) << "writeHeader failed to re-open for write"; return false; } } initHeader(); } uint64_t oldIV = externalIV; externalIV = iv; if(!writeHeader()) { externalIV = oldIV; return false; } } return base->setIV( iv ); } off_t CipherFileIO::adjustedSize(off_t rawSize) const { off_t size = rawSize; if (rawSize >= headerLen) size -= headerLen; return size; } int CipherFileIO::getAttr( struct stat *stbuf ) const { int res = base->getAttr( stbuf ); // adjust size if we have a file header if((res == 0) && S_ISREG(stbuf->st_mode)) stbuf->st_size = adjustedSize(stbuf->st_size); return res; } off_t CipherFileIO::getSize() const { // No check on S_ISREG here -- getSize only for normal files! off_t size = base->getSize(); return adjustedSize(size); } void CipherFileIO::initHeader( ) { int cbs = cipher->cipherBlockSize(); MemBlock mb; mb.allocate(cbs); // check if the file has a header, and read it if it does.. Otherwise, // create one. off_t rawSize = base->getSize(); if(rawSize >= headerLen) { VLOG(1) << "reading existing header, rawSize = " << rawSize; IORequest req; req.offset = 0; req.data = mb.data; req.dataLen = sizeof(uint64_t); base->read( req ); if (perFileIV) { cipher->streamDecode( mb.data, sizeof(uint64_t), externalIV ); fileIV = 0; for(unsigned int i=0; ipseudoRandomize( mb.data, 8 )) throw Error("Unable to generate a random file IV"); fileIV = 0; for(unsigned int i=0; istreamEncode( mb.data, sizeof(uint64_t), externalIV ); if( base->isWritable() ) { IORequest req; req.offset = 0; req.data = mb.data; req.dataLen = sizeof(uint64_t); base->write( req ); } else VLOG(1) << "base not writable, IV not written.."; } VLOG(1) << "initHeader finished, fileIV = " << fileIV; } bool CipherFileIO::writeHeader( ) { if( !base->isWritable() ) { // open for write.. int newFlags = lastFlags | O_RDWR; if( base->open( newFlags ) < 0 ) { VLOG(1) << "writeHeader failed to re-open for write"; return false; } } LOG_IF(ERROR, fileIV == 0) << "Internal error: fileIV == 0 in writeHeader!!!"; VLOG(1) << "writing fileIV " << fileIV; MemBlock mb; mb.allocate(headerLen); if (perFileIV) { unsigned char *buf = mb.data; for(int i=sizeof(buf)-1; i>=0; --i) { buf[i] = (unsigned char)(fileIV & 0xff); fileIV >>= 8; } cipher->streamEncode( buf, sizeof(uint64_t), externalIV ); } IORequest req; req.offset = 0; req.data = mb.data; req.dataLen = headerLen; base->write( req ); return true; } ssize_t CipherFileIO::readOneBlock( const IORequest &req ) const { // read raw data, then decipher it.. int bs = blockSize(); rAssert(req.dataLen <= bs); off_t blockNum = req.offset / bs; ssize_t readSize = 0; IORequest tmpReq = req; MemBlock mb; tmpReq.offset += headerLen; int maxReadSize = req.dataLen; readSize = base->read( tmpReq ); bool ok; if(readSize > 0) { if(headerLen != 0 && fileIV == 0) const_cast(this)->initHeader(); if(readSize == bs) { ok = blockRead( tmpReq.data, bs, blockNum ^ fileIV); } else { ok = streamRead( tmpReq.data, (int)readSize, blockNum ^ fileIV); } if(!ok) { VLOG(1) << "decodeBlock failed for block " << blockNum << ", size " << readSize; readSize = -1; } else if (tmpReq.data != req.data) { if (readSize > maxReadSize) readSize = maxReadSize; memcpy(req.data, tmpReq.data, readSize); } } else VLOG(1) << "readSize zero for offset " << req.offset; return readSize; } bool CipherFileIO::writeOneBlock( const IORequest &req ) { int bs = blockSize(); off_t blockNum = req.offset / bs; if(headerLen != 0 && fileIV == 0) initHeader(); MemBlock mb; bool ok; if (req.dataLen == bs) { ok = blockWrite( req.data, bs, blockNum ^ fileIV ); } else { ok = streamWrite( req.data, (int)req.dataLen, blockNum ^ fileIV ); } if( ok ) { if(headerLen != 0) { IORequest nreq = req; if (mb.data == NULL) { nreq.offset += headerLen; } else { // Partial block is stored at front of file. nreq.offset = 0; nreq.data = mb.data; nreq.dataLen = bs; base->truncate(req.offset + req.dataLen + headerLen); } ok = base->write( nreq ); } else ok = base->write( req ); } else { VLOG(1) << "encodeBlock failed for block " << blockNum << ", size " << req.dataLen; ok = false; } return ok; } bool CipherFileIO::blockWrite( unsigned char *buf, int size, uint64_t _iv64 ) const { if (!fsConfig->reverseEncryption) return cipher->blockEncode( buf, size, _iv64 ); else return cipher->blockDecode( buf, size, _iv64 ); } bool CipherFileIO::streamWrite( unsigned char *buf, int size, uint64_t _iv64 ) const { if (!fsConfig->reverseEncryption) return cipher->streamEncode( buf, size, _iv64 ); else return cipher->streamDecode( buf, size, _iv64 ); } bool CipherFileIO::blockRead( unsigned char *buf, int size, uint64_t _iv64 ) const { if (fsConfig->reverseEncryption) return cipher->blockEncode( buf, size, _iv64 ); else if(_allowHoles) { // special case - leave all 0's alone for(int i=0; iblockDecode( buf, size, _iv64 ); return true; } else return cipher->blockDecode( buf, size, _iv64 ); } bool CipherFileIO::streamRead( unsigned char *buf, int size, uint64_t _iv64 ) const { if (fsConfig->reverseEncryption) return cipher->streamEncode( buf, size, _iv64 ); else return cipher->streamDecode( buf, size, _iv64 ); } int CipherFileIO::truncate( off_t size ) { rAssert(size >= 0); if(headerLen == 0) { return blockTruncate( size, base.get() ); } else if(0 == fileIV) { // empty file.. create the header.. if( !base->isWritable() ) { // open for write.. int newFlags = lastFlags | O_RDWR; if( base->open( newFlags ) < 0 ) VLOG(1) << "writeHeader failed to re-open for write"; } initHeader(); } // can't let BlockFileIO call base->truncate(), since it would be using // the wrong size.. int res = blockTruncate( size, 0 ); if(res == 0) base->truncate( size + headerLen ); return res; } bool CipherFileIO::isWritable() const { return base->isWritable(); } } // namespace encfs