1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 package javax.crypto;
19 
20 import java.io.BufferedInputStream;
21 import java.io.FilterInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.security.GeneralSecurityException;
25 import libcore.io.Streams;
26 
27 /**
28  * This class wraps an {@code InputStream} and a cipher so that {@code read()}
29  * methods return data that are read from the underlying {@code InputStream} and
30  * processed by the cipher.
31  * <p>
32  * The cipher must be initialized for the requested operation before being used
33  * by a {@code CipherInputStream}. For example, if a cipher initialized for
34  * decryption is used with a {@code CipherInputStream}, the {@code
35  * CipherInputStream} tries to read the data an decrypt them before returning.
36  */
37 public class CipherInputStream extends FilterInputStream {
38     private final Cipher cipher;
39     private final byte[] inputBuffer;
40     private byte[] outputBuffer;
41     private int outputIndex; // index of the first byte to return from outputBuffer
42     private int outputLength; // count of the bytes to return from outputBuffer
43     private boolean finished;
44 
45     /**
46      * Creates a new {@code CipherInputStream} instance for an {@code
47      * InputStream} and a cipher.
48      *
49      * <p><strong>Warning:</strong> passing a null source creates an invalid
50      * {@code CipherInputStream}. All read operations on such a stream will
51      * fail.
52      *
53      * @param is
54      *            the input stream to read data from.
55      * @param c
56      *            the cipher to process the data with.
57      */
CipherInputStream(InputStream is, Cipher c)58     public CipherInputStream(InputStream is, Cipher c) {
59         super(is);
60         this.cipher = c;
61         int blockSize = Math.max(c.getBlockSize(), 1);
62         int bufferSize = Math.max(blockSize,
63                 BufferedInputStream.DEFAULT_BUFFER_SIZE / blockSize * blockSize);
64         inputBuffer = new byte[bufferSize];
65         outputBuffer = new byte[bufferSize + ((blockSize > 1) ? 2 * blockSize : 0)];
66     }
67 
68     /**
69      * Creates a new {@code CipherInputStream} instance for an {@code
70      * InputStream} without a cipher.
71      * <p>
72      * A {@code NullCipher} is created and used to process the data.
73      *
74      * @param is
75      *            the input stream to read data from.
76      */
CipherInputStream(InputStream is)77     protected CipherInputStream(InputStream is) {
78         this(is, new NullCipher());
79     }
80 
81     /**
82      * Attempts to fill the input buffer and process some data through the
83      * cipher. Returns {@code true} if output from the cipher is available to
84      * use.
85      */
fillBuffer()86     private boolean fillBuffer() throws IOException {
87         if (finished) {
88             return false;
89         }
90         outputIndex = 0;
91         outputLength = 0;
92         while (outputLength == 0) {
93             // check output size on each iteration since pending state
94             // in the cipher can cause this to vary from call to call
95             int outputSize = cipher.getOutputSize(inputBuffer.length);
96             if ((outputBuffer == null) || (outputBuffer.length < outputSize)) {
97                 this.outputBuffer = new byte[outputSize];
98             }
99             int byteCount = in.read(inputBuffer);
100             if (byteCount == -1) {
101                 try {
102                     outputLength = cipher.doFinal(outputBuffer, 0);
103                 } catch (Exception e) {
104                     throw new IOException("Error while finalizing cipher", e);
105                 }
106                 finished = true;
107                 return outputLength != 0;
108             }
109             try {
110                 outputLength = cipher.update(inputBuffer, 0, byteCount, outputBuffer, 0);
111             } catch (ShortBufferException e) {
112                 throw new AssertionError(e);  // should not happen since we sized with getOutputSize
113             }
114         }
115         return true;
116     }
117 
118     /**
119      * Reads the next byte from this cipher input stream.
120      *
121      * @return the next byte, or {@code -1} if the end of the stream is reached.
122      * @throws IOException
123      *             if an error occurs.
124      */
125     @Override
read()126     public int read() throws IOException {
127         if (in == null) {
128             throw new NullPointerException("in == null");
129         }
130         if (outputIndex == outputLength && !fillBuffer()) {
131             return -1;
132         }
133         return outputBuffer[outputIndex++] & 0xFF;
134     }
135 
136     /**
137      * Reads the next {@code len} bytes from this input stream into buffer
138      * {@code buf} starting at offset {@code off}.
139      * <p>
140      * if {@code buf} is {@code null}, the next {@code len} bytes are read and
141      * discarded.
142      *
143      * @return the number of bytes filled into buffer {@code buf}, or {@code -1}
144      *         of the of the stream is reached.
145      * @throws IOException
146      *             if an error occurs.
147      * @throws NullPointerException
148      *             if the underlying input stream is {@code null}.
149      */
150     @Override
read(byte[] buf, int off, int len)151     public int read(byte[] buf, int off, int len) throws IOException {
152         if (in == null) {
153             throw new NullPointerException("in == null");
154         }
155         if (outputIndex == outputLength && !fillBuffer()) {
156             return -1;
157         }
158         int available = outputLength - outputIndex;
159         if (available < len) {
160             len = available;
161         }
162         if (buf != null) {
163             System.arraycopy(outputBuffer, outputIndex, buf, off, len);
164         }
165         outputIndex += len;
166         return len;
167     }
168 
169     @Override
skip(long byteCount)170     public long skip(long byteCount) throws IOException {
171         return Streams.skipByReading(this, byteCount);
172     }
173 
174     @Override
available()175     public int available() throws IOException {
176         return outputLength - outputIndex;
177     }
178 
179     /**
180      * Closes this {@code CipherInputStream}, also closes the underlying input
181      * stream and call {@code doFinal} on the cipher object.
182      *
183      * @throws IOException
184      *             if an error occurs.
185      */
186     @Override
close()187     public void close() throws IOException {
188         in.close();
189         try {
190             cipher.doFinal();
191         } catch (GeneralSecurityException ignore) {
192             //do like RI does
193         }
194 
195     }
196 
197     /**
198      * Returns whether this input stream supports {@code mark} and
199      * {@code reset}, which it does not.
200      *
201      * @return false, since this input stream does not support {@code mark} and
202      *         {@code reset}.
203      */
204     @Override
markSupported()205     public boolean markSupported() {
206         return false;
207     }
208 }
209