package org.xmind.core.internal.security; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.InvalidCipherTextException; public class BlockCipherInputStream extends FilterInputStream { private final BufferedBlockCipher cipher; private byte[] outBuffer; private int outOffset; private final boolean isStream; private boolean eof; private int lastRead = -1; public BlockCipherInputStream(InputStream in, BufferedBlockCipher cipher) { super(in); this.cipher = cipher; isStream = cipher.getBlockSize() == 1; eof = false; } public int available() throws IOException { if (isStream) return super.available(); if (outBuffer == null || outOffset >= outBuffer.length) nextBlock(); return outBuffer.length - outOffset; } public synchronized void close() throws IOException { super.close(); } public synchronized int read() throws IOException { if (isStream) { byte[] buf = new byte[1]; int in = super.read(); if (in == -1) return -1; buf[0] = (byte) in; try { cipher.processBytes(buf, 0, 1, buf, 0); } catch (DataLengthException e) { throw new IOException(e.getMessage()); } return buf[0] & 0xFF; } if (outBuffer == null || outOffset >= outBuffer.length) { if (eof) return -1; nextBlock(); } if (outBuffer == null || outOffset >= outBuffer.length) return -1; return outBuffer[outOffset++] & 0xFF; } public synchronized int read(byte[] buf, int off, int len) throws IOException { if (buf == null) return (int) skip(len); if (isStream) { len = super.read(buf, off, len); if (len > 0) { try { cipher.processBytes(buf, off, len, buf, off); } catch (DataLengthException shouldNotHappen) { IOException ioe = new IOException( "Short buffer for stream cipher -- this should not happen"); //$NON-NLS-1$ ioe.initCause(shouldNotHappen); throw ioe; } } return len; } int count = 0; while (count < len) { if (outBuffer == null || outOffset >= outBuffer.length) { if (eof) { if (count == 0) count = -1; break; } nextBlock(); } if (outBuffer == null || outOffset >= outBuffer.length) { if (count == 0) count = -1; break; } int l = Math.min(outBuffer.length - outOffset, len - count); System.arraycopy(outBuffer, outOffset, buf, count + off, l); count += l; outOffset += l; } return count; } public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } public long skip(long bytes) throws IOException { if (isStream) { return super.skip(bytes); } long ret = 0; if (bytes > 0 && outBuffer != null && outOffset >= outBuffer.length) { ret = outBuffer.length - outOffset; outOffset = outBuffer.length; } return ret; } public boolean markSupported() { return false; } public void mark(int mark) { } public void reset() throws IOException { throw new IOException("reset not supported"); //$NON-NLS-1$ } private void nextBlock() throws IOException { byte[] buf = new byte[cipher.getBlockSize()]; byte[] out = new byte[cipher.getOutputSize(buf.length)]; try { outBuffer = null; outOffset = 0; while (outBuffer == null) { int len = in.read(buf); if (len == -1) { if (lastRead > 0) { int num = cipher.doFinal(out, 0); if (num > 0) { outBuffer = new byte[num]; System.arraycopy(out, 0, outBuffer, outOffset, num); } } eof = true; return; } lastRead = len; outOffset = 0; int num = cipher.processBytes(buf, 0, len, out, 0); if (num > 0) { outBuffer = new byte[num]; System.arraycopy(out, 0, outBuffer, outOffset, num); } } } catch (DataLengthException bpe) { IOException ioe = new IOException("illegal block size"); //$NON-NLS-1$ ioe.initCause(bpe); throw ioe; } catch (InvalidCipherTextException ibse) { IOException ioe = new IOException("bad padding"); //$NON-NLS-1$ ioe.initCause(ibse); throw ioe; } finally { } } }