/* * Copyright 2010-2016 Brian Pellin. * * This file is part of KeePassDroid. * * KeePassDroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * KeePassDroid 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>. * */ package com.keepassdroid.stream; import com.keepassdroid.utils.Types; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class HashedBlockInputStream extends InputStream { private final static int HASH_SIZE = 32; private LEDataInputStream baseStream; private int bufferPos = 0; private byte[] buffer = new byte[0]; private long bufferIndex = 0; private boolean atEnd = false; @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } public HashedBlockInputStream(InputStream is) { baseStream = new LEDataInputStream(is); } @Override public int read(byte[] b, int offset, int length) throws IOException { if ( atEnd ) return -1; int remaining = length; while ( remaining > 0 ) { if ( bufferPos == buffer.length ) { // Get more from the source into the buffer if ( ! ReadHashedBlock() ) { return length - remaining; } } // Copy from buffer out int copyLen = Math.min(buffer.length - bufferPos, remaining); System.arraycopy(buffer, bufferPos, b, offset, copyLen); offset += copyLen; bufferPos += copyLen; remaining -= copyLen; } return length; } /** * @return false, when the end of the source stream is reached * @throws IOException */ private boolean ReadHashedBlock() throws IOException { if ( atEnd ) return false; bufferPos = 0; long index = baseStream.readUInt(); if ( index != bufferIndex ) { throw new IOException("Invalid data format"); } bufferIndex++; byte[] storedHash = baseStream.readBytes(32); if ( storedHash == null || storedHash.length != HASH_SIZE) { throw new IOException("Invalid data format"); } int bufferSize = LEDataInputStream.readInt(baseStream); if ( bufferSize < 0 ) { throw new IOException("Invalid data format"); } if ( bufferSize == 0 ) { for (int hash = 0; hash < HASH_SIZE; hash++) { if ( storedHash[hash] != 0 ) { throw new IOException("Invalid data format"); } } atEnd = true; buffer = new byte[0]; return false; } buffer = baseStream.readBytes(bufferSize); if ( buffer == null || buffer.length != bufferSize ) { throw new IOException("Invalid data format"); } MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new IOException("SHA-256 not implemented here."); } byte[] computedHash = md.digest(buffer); if ( computedHash == null || computedHash.length != HASH_SIZE ) { throw new IOException("Hash wrong size"); } if ( ! Arrays.equals(storedHash, computedHash) ) { throw new IOException("Hashes didn't match."); } return true; } @Override public long skip(long n) throws IOException { return 0; } @Override public int read() throws IOException { if ( atEnd ) return -1; if ( bufferPos == buffer.length ) { if ( ! ReadHashedBlock() ) return -1; } int output = Types.readUByte(buffer, bufferPos); bufferPos++; return output; } @Override public void close() throws IOException { baseStream.close(); } }