/* * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.crypto; import java.io.InputStream; import java.io.FilterInputStream; import java.io.IOException; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; /** * A CipherInputStream is composed of an InputStream and a Cipher so * that read() methods return data that are read in from the * underlying InputStream but have been additionally processed by the * Cipher. The Cipher must be fully initialized before being used by * a CipherInputStream. * * <p> For example, if the Cipher is initialized for decryption, the * CipherInputStream will attempt to read in data and decrypt them, * before returning the decrypted data. * * <p> This class adheres strictly to the semantics, especially the * failure semantics, of its ancestor classes * java.io.FilterInputStream and java.io.InputStream. This class has * exactly those methods specified in its ancestor classes, and * overrides them all. Moreover, this class catches all exceptions * that are not thrown by its ancestor classes. In particular, the * <code>skip</code> method skips, and the <code>available</code> * method counts only data that have been processed by the encapsulated Cipher. * * <p> It is crucial for a programmer using this class not to use * methods that are not defined or overriden in this class (such as a * new method or constructor that is later added to one of the super * classes), because the design and implementation of those methods * are unlikely to have considered security impact with regard to * CipherInputStream. * * @author Li Gong * @see java.io.InputStream * @see java.io.FilterInputStream * @see javax.crypto.Cipher * @see javax.crypto.CipherOutputStream * * @since 1.4 */ public class CipherInputStream extends FilterInputStream { // the cipher engine to use to process stream data private Cipher cipher; // the underlying input stream private InputStream input; /* the buffer holding data that have been read in from the underlying stream, but have not been processed by the cipher engine. the size 512 bytes is somewhat randomly chosen */ private byte[] ibuffer = new byte[512]; // having reached the end of the underlying input stream private boolean done = false; /* the buffer holding data that have been processed by the cipher engine, but have not been read out */ private byte[] obuffer; // the offset pointing to the next "new" byte private int ostart = 0; // the offset pointing to the last "new" byte private int ofinish = 0; // stream status private boolean closed = false; /** * private convenience function. * * Entry condition: ostart = ofinish * * Exit condition: ostart <= ofinish * * return (ofinish-ostart) (we have this many bytes for you) * return 0 (no data now, but could have more later) * return -1 (absolutely no more data) * * Note: Exceptions are only thrown after the stream is completely read. * For AEAD ciphers a read() of any length will internally cause the * whole stream to be read fully and verify the authentication tag before * returning decrypted data or exceptions. */ private int getMoreData() throws IOException { if (done) return -1; int readin = input.read(ibuffer); if (readin == -1) { done = true; try { obuffer = cipher.doFinal(); } catch (IllegalBlockSizeException | BadPaddingException e) { obuffer = null; throw new IOException(e); } if (obuffer == null) return -1; else { ostart = 0; ofinish = obuffer.length; return ofinish; } } try { obuffer = cipher.update(ibuffer, 0, readin); } catch (IllegalStateException e) { obuffer = null; throw e; } ostart = 0; if (obuffer == null) ofinish = 0; else ofinish = obuffer.length; return ofinish; } /** * Constructs a CipherInputStream from an InputStream and a * Cipher. * <br>Note: if the specified input stream or cipher is * null, a NullPointerException may be thrown later when * they are used. * @param is the to-be-processed input stream * @param c an initialized Cipher object */ public CipherInputStream(InputStream is, Cipher c) { super(is); input = is; cipher = c; } /** * Constructs a CipherInputStream from an InputStream without * specifying a Cipher. This has the effect of constructing a * CipherInputStream using a NullCipher. * <br>Note: if the specified input stream is null, a * NullPointerException may be thrown later when it is used. * @param is the to-be-processed input stream */ protected CipherInputStream(InputStream is) { super(is); input = is; cipher = new NullCipher(); } /** * Reads the next byte of data from this input stream. The value * byte is returned as an <code>int</code> in the range * <code>0</code> to <code>255</code>. If no byte is available * because the end of the stream has been reached, the value * <code>-1</code> is returned. This method blocks until input data * is available, the end of the stream is detected, or an exception * is thrown. * * @return the next byte of data, or <code>-1</code> if the end of the * stream is reached. * @exception IOException if an I/O error occurs. */ public int read() throws IOException { if (ostart >= ofinish) { // we loop for new data as the spec says we are blocking int i = 0; while (i == 0) i = getMoreData(); if (i == -1) return -1; } return ((int) obuffer[ostart++] & 0xff); }; /** * Reads up to <code>b.length</code> bytes of data from this input * stream into an array of bytes. * <p> * The <code>read</code> method of <code>InputStream</code> calls * the <code>read</code> method of three arguments with the arguments * <code>b</code>, <code>0</code>, and <code>b.length</code>. * * @param b the buffer into which the data is read. * @return the total number of bytes read into the buffer, or * <code>-1</code> is there is no more data because the end of * the stream has been reached. * @exception IOException if an I/O error occurs. * @see java.io.InputStream#read(byte[], int, int) */ public int read(byte b[]) throws IOException { return read(b, 0, b.length); } /** * Reads up to <code>len</code> bytes of data from this input stream * into an array of bytes. This method blocks until some input is * available. If the first argument is <code>null,</code> up to * <code>len</code> bytes are read and discarded. * * @param b the buffer into which the data is read. * @param off the start offset in the destination array * <code>buf</code> * @param len the maximum number of bytes read. * @return the total number of bytes read into the buffer, or * <code>-1</code> if there is no more data because the end of * the stream has been reached. * @exception IOException if an I/O error occurs. * @see java.io.InputStream#read() */ public int read(byte b[], int off, int len) throws IOException { if (ostart >= ofinish) { // we loop for new data as the spec says we are blocking int i = 0; while (i == 0) i = getMoreData(); if (i == -1) return -1; } if (len <= 0) { return 0; } int available = ofinish - ostart; if (len < available) available = len; if (b != null) { System.arraycopy(obuffer, ostart, b, off, available); } ostart = ostart + available; return available; } /** * Skips <code>n</code> bytes of input from the bytes that can be read * from this input stream without blocking. * * <p>Fewer bytes than requested might be skipped. * The actual number of bytes skipped is equal to <code>n</code> or * the result of a call to * {@link #available() available}, * whichever is smaller. * If <code>n</code> is less than zero, no bytes are skipped. * * <p>The actual number of bytes skipped is returned. * * @param n the number of bytes to be skipped. * @return the actual number of bytes skipped. * @exception IOException if an I/O error occurs. */ public long skip(long n) throws IOException { int available = ofinish - ostart; if (n > available) { n = available; } if (n < 0) { return 0; } ostart += n; return n; } /** * Returns the number of bytes that can be read from this input * stream without blocking. The <code>available</code> method of * <code>InputStream</code> returns <code>0</code>. This method * <B>should</B> be overridden by subclasses. * * @return the number of bytes that can be read from this input stream * without blocking. * @exception IOException if an I/O error occurs. */ public int available() throws IOException { return (ofinish - ostart); } /** * Closes this input stream and releases any system resources * associated with the stream. * <p> * The <code>close</code> method of <code>CipherInputStream</code> * calls the <code>close</code> method of its underlying input * stream. * * @exception IOException if an I/O error occurs. */ public void close() throws IOException { if (closed) { return; } closed = true; input.close(); // Throw away the unprocessed data and throw no crypto exceptions. // AEAD ciphers are fully readed before closing. Any authentication // exceptions would occur while reading. if (!done) { try { cipher.doFinal(); } catch (BadPaddingException | IllegalBlockSizeException ex) { // Catch exceptions as the rest of the stream is unused. } } ostart = 0; ofinish = 0; } /** * Tests if this input stream supports the <code>mark</code> * and <code>reset</code> methods, which it does not. * * @return <code>false</code>, since this class does not support the * <code>mark</code> and <code>reset</code> methods. * @see java.io.InputStream#mark(int) * @see java.io.InputStream#reset() */ public boolean markSupported() { return false; } }