Home | History | Annotate | Download | only in crypto
      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      */
     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      */
     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      */
     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
    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
    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
    170     public long skip(long byteCount) throws IOException {
    171         return Streams.skipByReading(this, byteCount);
    172     }
    173 
    174     @Override
    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
    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
    205     public boolean markSupported() {
    206         return false;
    207     }
    208 }
    209