| /* CipherInputStream.java -- Filters input through a cipher. |
| Copyright (C) 2004 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath 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, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package javax.crypto; |
| |
| import java.io.FilterInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| /** |
| * This is an {@link java.io.InputStream} that filters its data |
| * through a {@link Cipher} before returning it. The <code>Cipher</code> |
| * argument must have been initialized before it is passed to the |
| * constructor. |
| * |
| * @author Casey Marshall (csm@gnu.org) |
| */ |
| public class CipherInputStream extends FilterInputStream |
| { |
| |
| // Constants and variables. |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * The underlying {@link Cipher} instance. |
| */ |
| private Cipher cipher; |
| |
| /** |
| * Data that has been transformed but not read. |
| */ |
| private byte[] outBuffer; |
| |
| /** |
| * The offset into {@link #outBuffer} where valid data starts. |
| */ |
| private int outOffset; |
| |
| /** |
| * The number of valid bytes in the {@link #outBuffer}. |
| */ |
| private int outLength; |
| |
| /** |
| * Byte buffer that is filled with raw data from the underlying input |
| * stream. |
| */ |
| private byte[][] inBuffer; |
| |
| /** |
| * The amount of bytes in inBuffer[0] that may be input to the cipher. |
| */ |
| private int inLength; |
| |
| /** |
| * We set this when the cipher block size is 1, meaning that we can |
| * transform any amount of data. |
| */ |
| private boolean isStream; |
| |
| private static final int VIRGIN = 0; // I am born. |
| private static final int LIVING = 1; // I am nailed to the hull. |
| private static final int DYING = 2; // I am eaten by sharks. |
| private static final int DEAD = 3; |
| private int state; |
| |
| // Constructors. |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Creates a new input stream with a source input stream and cipher. |
| * |
| * @param in The underlying input stream. |
| * @param cipher The cipher to filter data through. |
| */ |
| public CipherInputStream(InputStream in, Cipher cipher) |
| { |
| this(in); |
| this.cipher = cipher; |
| if (!(isStream = cipher.getBlockSize() == 1)) |
| { |
| inBuffer = new byte[2][]; |
| inBuffer[0] = new byte[cipher.getBlockSize()]; |
| inBuffer[1] = new byte[cipher.getBlockSize()]; |
| inLength = 0; |
| outBuffer = new byte[cipher.getBlockSize()]; |
| outOffset = outLength = 0; |
| state = VIRGIN; |
| } |
| } |
| |
| /** |
| * Creates a new input stream without a cipher. This constructor is |
| * <code>protected</code> because this class does not work without an |
| * underlying cipher. |
| * |
| * @param in The underlying input stream. |
| */ |
| protected CipherInputStream(InputStream in) |
| { |
| super(in); |
| } |
| |
| // Instance methods overriding java.io.FilterInputStream. |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Returns the number of bytes available without blocking. The value |
| * returned by this method is never greater than the underlying |
| * cipher's block size. |
| * |
| * @return The number of bytes immediately available. |
| * @throws java.io.IOException If an I/O exception occurs. |
| */ |
| public int available() throws IOException |
| { |
| if (isStream) |
| return super.available(); |
| return outLength - outOffset; |
| } |
| |
| /** |
| * Close this input stream. This method merely calls the {@link |
| * java.io.InputStream#close()} method of the underlying input stream. |
| * |
| * @throws java.io.IOException If an I/O exception occurs. |
| */ |
| public void close() throws IOException |
| { |
| super.close(); |
| } |
| |
| /** |
| * Read a single byte from this input stream; returns -1 on the |
| * end-of-file. |
| * |
| * @return The byte read, or -1 if there are no more bytes. |
| * @throws java.io.IOExcpetion If an I/O exception occurs. |
| */ |
| public 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.update(buf, 0, 1, buf, 0); |
| } |
| catch (ShortBufferException shouldNotHappen) |
| { |
| throw new IOException(shouldNotHappen.getMessage()); |
| } |
| return buf[0] & 0xFF; |
| } |
| if (state == DEAD) return -1; |
| if (available() == 0) nextBlock(); |
| if (state == DEAD) return -1; |
| return outBuffer[outOffset++] & 0xFF; |
| } |
| |
| /** |
| * Read bytes into an array, returning the number of bytes read or -1 |
| * on the end-of-file. |
| * |
| * @param buf The byte array to read into. |
| * @param off The offset in <code>buf</code> to start. |
| * @param len The maximum number of bytes to read. |
| * @return The number of bytes read, or -1 on the end-of-file. |
| * @throws java.io.IOException If an I/O exception occurs. |
| */ |
| public int read(byte[] buf, int off, int len) throws IOException |
| { |
| if (isStream) |
| { |
| len = super.read(buf, off, len); |
| try |
| { |
| cipher.update(buf, off, len, buf, off); |
| } |
| catch (ShortBufferException shouldNotHappen) |
| { |
| throw new IOException(shouldNotHappen.getMessage()); |
| } |
| return len; |
| } |
| |
| int count = 0; |
| while (count < len) |
| { |
| if (available() == 0) |
| nextBlock(); |
| if (state == DEAD) |
| { |
| if (count > 0) return count; |
| else return -1; |
| } |
| int l = Math.min(available(), len - count); |
| System.arraycopy(outBuffer, outOffset, buf, count+off, l); |
| count += l; |
| outOffset = outLength = 0; |
| } |
| return count; |
| } |
| |
| /** |
| * Read bytes into an array, returning the number of bytes read or -1 |
| * on the end-of-file. |
| * |
| * @param buf The byte arry to read into. |
| * @return The number of bytes read, or -1 on the end-of-file. |
| * @throws java.io.IOException If an I/O exception occurs. |
| */ |
| public int read(byte[] buf) throws IOException |
| { |
| return read(buf, 0, buf.length); |
| } |
| |
| /** |
| * Skip a number of bytes. This class only supports skipping as many |
| * bytes as are returned by {@link #available()}, which is the number |
| * of transformed bytes currently in this class's internal buffer. |
| * |
| * @param bytes The number of bytes to skip. |
| * @return The number of bytes skipped. |
| */ |
| public long skip(long bytes) throws IOException |
| { |
| if (isStream) |
| { |
| return super.skip(bytes); |
| } |
| long ret = 0; |
| if (bytes > 0 && available() > 0) |
| { |
| ret = available(); |
| outOffset = outLength = 0; |
| } |
| return ret; |
| } |
| |
| /** |
| * Returns whether or not this input stream supports the {@link |
| * #mark(long)} and {@link #reset()} methods; this input stream does |
| * not, however, and invariably returns <code>false</code>. |
| * |
| * @return <code>false</code> |
| */ |
| public boolean markSupported() |
| { |
| return false; |
| } |
| |
| /** |
| * Set the mark. This method is unsupported and is empty. |
| * |
| * @param mark Is ignored. |
| */ |
| public void mark(int mark) |
| { |
| } |
| |
| /** |
| * Reset to the mark. This method is unsupported and is empty. |
| */ |
| public void reset() throws IOException |
| { |
| throw new IOException("reset not supported"); |
| } |
| |
| // Own methods. |
| // ------------------------------------------------------------------------- |
| |
| private void nextBlock() throws IOException |
| { |
| byte[] temp = inBuffer[0]; |
| inBuffer[0] = inBuffer[1]; |
| inBuffer[1] = temp; |
| int count = 0; |
| boolean eof = false; |
| |
| if (state == VIRGIN || state == LIVING) |
| { |
| do |
| { |
| int l = in.read(inBuffer[1], count, inBuffer[1].length - count); |
| if (l == -1) |
| { |
| eof = true; |
| break; |
| } |
| count += l; |
| } |
| while (count < inBuffer[1].length); |
| } |
| |
| try |
| { |
| switch (state) |
| { |
| case VIRGIN: |
| state = LIVING; |
| nextBlock(); |
| break; |
| case LIVING: |
| if (eof) |
| { |
| if (count > 0) |
| { |
| outOffset = cipher.update(inBuffer[0], 0, inLength, outBuffer, 0); |
| state = DYING; |
| } |
| else |
| { |
| outOffset = cipher.doFinal(inBuffer[0], 0, inLength, outBuffer, 0); |
| state = DEAD; |
| } |
| } |
| else |
| { |
| outOffset = cipher.update(inBuffer[0], 0, inLength, outBuffer, 0); |
| } |
| break; |
| case DYING: |
| outOffset = cipher.doFinal(inBuffer[0], 0, inLength, outBuffer, 0); |
| state = DEAD; |
| break; |
| case DEAD: |
| } |
| } |
| catch (ShortBufferException sbe) |
| { |
| throw new IOException(sbe.toString()); |
| } |
| catch (BadPaddingException bpe) |
| { |
| throw new IOException(bpe.toString()); |
| } |
| catch (IllegalBlockSizeException ibse) |
| { |
| throw new IOException(ibse.toString()); |
| } |
| inLength = count; |
| } |
| } |