// Copyright (c) 2012-2014 Konstantin Isakov and ZBackup contributors, see CONTRIBUTORS // Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE #include #include #include "check.hh" #include "encrypted_file.hh" #include "endian.hh" #include "page_size.hh" #include "random.hh" #include "debug.hh" namespace EncryptedFile { using Encryption::BlockSize; InputStream::InputStream( char const * fileName, EncryptionKey const & key, void const * iv_ ): file( fileName, UnbufferedFile::ReadOnly ), filePos( 0 ), key( key ), // Our buffer must be larger than BlockSize, as otherwise we won't be able // to handle PKCS#7 padding properly buffer( std::max( getPageSize(), ( unsigned ) BlockSize * 2 ) ), fill( 0 ), remainder( 0 ), backedUp( false ) { dPrintf( "Loading %s, hasKey: %s\n", fileName, key.hasKey() ? "true" : "false" ); if ( key.hasKey() ) { memcpy( iv, iv_, sizeof( iv ) ); // Since we use padding, file size should be evenly dividable by the cipher // block size, and we should have at least one block UnbufferedFile::Offset size = file.size(); if ( !size || size % BlockSize ) throw exIncorrectFileSize(); } } bool InputStream::Next( void const ** data, int * size ) { // If we backed up, return the unconsumed data if ( backedUp ) backedUp = false; else { try { // Update adler32 for the previous block adler32.add( start, fill ); // Read more data if ( filePos && !remainder ) { // Once we're read a full block, we always have a remainder. If not, // this means we've hit the end of file already fill = 0; return false; } // If we have a remainder, move it to the beginning of buffer and make // it start the next block memmove( buffer.data(), start + fill, remainder ); start = buffer.data(); fill = file.read( start + remainder, buffer.size() - remainder ) + remainder; // remainder should techically be 0 now, but decrypt() will update it // anyway // remainder = 0; decrypt(); } catch( UnbufferedFile::exReadError & ) { fill = 0; // To make sure state is remaining consistent return false; } } *data = start; *size = fill; filePos += fill; return *size; } void InputStream::BackUp( int count ) { CHECK( count >= 0, "count is negative" ); if ( !backedUp ) { CHECK( (size_t) count <= fill, "Backing up too much" ); size_t consumed = fill - count; adler32.add( start, consumed ); start += consumed; fill = count; filePos -= count; backedUp = fill; // Don't make the next Next() return 0 bytes } else { CHECK( count == 0, "backing up after being backed up already" ); } } bool InputStream::Skip( int count ) { CHECK( count >= 0, "count is negative" ); // We always need to read and decrypt data, as otherwise both the state of // CBC and adler32 would be incorrect void const * data; int size; while( count ) { if ( !Next( &data, &size ) ) return false; else if ( size > count ) { BackUp( size - count ); break; } else count -= size; } return true; } int64_t InputStream::ByteCount() const { return filePos; } Adler32::Value InputStream::getAdler32() { // This makes all data consumed, if not already BackUp( 0 ); return adler32.result(); } void InputStream::read( void * buf, size_t size ) { void const * data; int avail; char * n = ( char * ) buf; while( size ) { if ( !Next( &data, &avail ) ) throw exReadFailed(); else if ( avail > ( ssize_t ) size ) { memcpy( n, data, size ); BackUp( avail - size ); break; } else { memcpy( n, data, avail ); n += avail; size -= avail; } } } void InputStream::checkAdler32() { Adler32::Value ours = getAdler32(); Adler32::Value r; read( &r, sizeof( r ) ); if ( ours != fromLittleEndian( r ) ) throw exAdlerMismatch(); } void InputStream::consumeRandomIv() { if ( key.hasKey() ) { char iv[ Encryption::IvSize ]; read( iv, sizeof( iv ) ); // read() can throw exceptions, Skip() can't } } void InputStream::decrypt() { if ( fill == buffer.size() ) { // When we have the full buffer, we set the last block of it aside and // treat the rest as the normal CBC sequence. The last block in the buffer // may be the last block of file, in which case we would need to handle // padding. That may happen the next time the function is called remainder = BlockSize; fill -= BlockSize; doDecrypt(); } else { // This is an end of file. Decrypt data treating the last block being // padded // Since we always have padding in the file and the last block is always // set apart when reading full buffers, we must have at least one block // to decrypt here doDecrypt(); // Unpad the last block if ( key.hasKey() ) fill -= BlockSize - Encryption::unpad( start + fill - BlockSize ); // We have not left any remainder this time remainder = 0; } } void InputStream::doDecrypt() { if ( !key.hasKey() ) return; // Since we use padding, file size should be evenly dividable by the cipher's // block size, and we should always have at least one block. When we get here, // we would always get the proper fill value unless those characteristics are // not met. We check for the same condition on construction, but the file // size can change while we are reading it // We don't throw an exception here as the interface we implement doesn't // support them CHECK( fill > 0 && !( fill % BlockSize ), "incorrect size of the encrypted " "file - must be non-zero and in multiples of %u", ( unsigned ) BlockSize ); // Copy the next iv prior to decrypting the data in place, as it will // not be available afterwards char newIv[ Encryption::IvSize ]; memcpy( newIv, Encryption::getNextDecryptionIv( start, fill ), sizeof( newIv ) ); // Decrypt the data Encryption::decrypt( iv, key.getKey(), start, start, fill ); // Copy the new iv memcpy( iv, newIv, sizeof( iv ) ); } OutputStream::OutputStream( char const * fileName, EncryptionKey const & key, void const * iv_ ): file( fileName, UnbufferedFile::WriteOnly ), filePos( 0 ), key( key ), buffer( getPageSize() ), start( buffer.data() ), avail( 0 ), backedUp( false ) { dPrintf( "Saving %s, hasKey: %s\n", fileName, key.hasKey() ? "true" : "false" ); if ( key.hasKey() ) memcpy( iv, iv_, sizeof( iv ) ); } bool OutputStream::Next( void ** data, int * size ) { // If we backed up, return the unconsumed data if ( backedUp ) backedUp = false; else { try { // Update adler32 for the previous block adler32.add( start, avail ); // Encrypt and write the buffer if it had data if ( filePos ) encryptAndWrite( buffer.size() ); start = buffer.data(); avail = buffer.size(); } catch( UnbufferedFile::exWriteError & ) { avail = 0; // To make sure state is remaining consistent return false; } } *data = start; *size = avail; filePos += avail; return *size; } void OutputStream::BackUp( int count ) { CHECK( count >= 0, "count is negative" ); if ( !backedUp ) { CHECK( (size_t) count <= avail, "Backing up too much" ); size_t consumed = avail - count; adler32.add( start, consumed ); start += consumed; avail = count; filePos -= count; backedUp = avail; // Don't make the next Next() return 0 bytes } else { CHECK( count == 0, "backing up after being backed up already" ); } } int64_t OutputStream::ByteCount() const { return filePos; } Adler32::Value OutputStream::getAdler32() { // This makes all data consumed, if not already BackUp( 0 ); return adler32.result(); } void OutputStream::write( void const * buf, size_t size ) { void * data; int avail; char const * n = ( char const * ) buf; while( size ) { if ( !Next( &data, &avail ) ) throw exReadFailed(); else if ( avail > ( ssize_t ) size ) { memcpy( data, n, size ); BackUp( avail - size ); break; } else { memcpy( data, n, avail ); n += avail; size -= avail; } } } void OutputStream::writeAdler32() { Adler32::Value v = toLittleEndian( getAdler32() ); write( &v, sizeof( v ) ); } void OutputStream::writeRandomIv() { if ( key.hasKey() ) { char iv[ Encryption::IvSize ]; Random::generatePseudo( iv, sizeof( iv ) ); write( iv, sizeof( iv ) ); } } void OutputStream::encryptAndWrite( size_t bytes ) { if ( key.hasKey() ) { CHECK( bytes > 0 && !( bytes % BlockSize ), "incorrect number of bytes to " "encrypt and write - must be non-zero and in multiples of %u", ( unsigned ) BlockSize ); void const * nextIv = Encryption::encrypt( iv, key.getKey(), buffer.data(), buffer.data(), bytes ); memcpy( iv, nextIv, sizeof( iv ) ); } file.write( buffer.data(), bytes ); } OutputStream::~OutputStream() { // This makes all data consumed, if not already BackUp( 0 ); // If we have the full buffer, write it first if ( start == buffer.data() + buffer.size() ) { encryptAndWrite( buffer.size() ); start = buffer.data(); } size_t bytesToWrite = start - buffer.data(); if ( key.hasKey() ) { // Perform padding size_t remainderSize = bytesToWrite % BlockSize; Encryption::pad( start - remainderSize, remainderSize ); bytesToWrite += BlockSize - remainderSize; } encryptAndWrite( bytesToWrite ); } }