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