001    /* CipherInputStream.java -- Filters input through a cipher.
002       Copyright (C) 2004  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.crypto;
040    
041    import gnu.classpath.Configuration;
042    import gnu.classpath.debug.Component;
043    import gnu.classpath.debug.SystemLogger;
044    
045    import java.io.FilterInputStream;
046    import java.io.IOException;
047    import java.io.InputStream;
048    
049    import java.util.logging.Logger;
050    
051    /**
052     * This is an {@link java.io.InputStream} that filters its data
053     * through a {@link Cipher} before returning it. The <code>Cipher</code>
054     * argument must have been initialized before it is passed to the
055     * constructor.
056     *
057     * @author Casey Marshall (csm@gnu.org)
058     */
059    public class CipherInputStream extends FilterInputStream
060    {
061    
062      // Constants and variables.
063      // ------------------------------------------------------------------------
064    
065      private static final Logger logger = SystemLogger.SYSTEM;
066    
067      /**
068       * The underlying {@link Cipher} instance.
069       */
070      private final Cipher cipher;
071    
072      /**
073       * Data that has been transformed but not read.
074       */
075      private byte[] outBuffer;
076    
077      /**
078       * The offset into {@link #outBuffer} where valid data starts.
079       */
080      private int outOffset;
081    
082      /**
083       * We set this when the cipher block size is 1, meaning that we can
084       * transform any amount of data.
085       */
086      private final boolean isStream;
087    
088      /**
089       * Whether or not we've reached the end of the stream.
090       */
091      private boolean eof;
092    
093      // Constructors.
094      // ------------------------------------------------------------------------
095    
096      /**
097       * Creates a new input stream with a source input stream and cipher.
098       *
099       * @param in     The underlying input stream.
100       * @param cipher The cipher to filter data through.
101       */
102      public CipherInputStream(InputStream in, Cipher cipher)
103      {
104        super (in);
105        this.cipher = cipher;
106        isStream = cipher.getBlockSize () == 1;
107        eof = false;
108        if (Configuration.DEBUG)
109          logger.log (Component.CRYPTO, "I am born; cipher: {0}, stream? {1}",
110                      new Object[] { cipher.getAlgorithm (),
111                                     Boolean.valueOf (isStream) });
112      }
113    
114      /**
115       * Creates a new input stream without a cipher. This constructor is
116       * <code>protected</code> because this class does not work without an
117       * underlying cipher.
118       *
119       * @param in The underlying input stream.
120       */
121      protected CipherInputStream(InputStream in)
122      {
123        this (in, new NullCipher ());
124      }
125    
126      // Instance methods overriding java.io.FilterInputStream.
127      // ------------------------------------------------------------------------
128    
129      /**
130       * Returns the number of bytes available without blocking. The value
131       * returned is the number of bytes that have been processed by the
132       * cipher, and which are currently buffered by this class.
133       *
134       * @return The number of bytes immediately available.
135       * @throws java.io.IOException If an I/O exception occurs.
136       */
137      public int available() throws IOException
138      {
139        if (isStream)
140          return super.available();
141        if (outBuffer == null || outOffset >= outBuffer.length)
142          nextBlock ();
143        return outBuffer.length - outOffset;
144      }
145    
146      /**
147       * Close this input stream. This method merely calls the {@link
148       * java.io.InputStream#close()} method of the underlying input stream.
149       *
150       * @throws java.io.IOException If an I/O exception occurs.
151       */
152      public synchronized void close() throws IOException
153      {
154        super.close();
155      }
156    
157      /**
158       * Read a single byte from this input stream; returns -1 on the
159       * end-of-file.
160       *
161       * @return The byte read, or -1 if there are no more bytes.
162       * @throws java.io.IOExcpetion If an I/O exception occurs.
163       */
164      public synchronized int read() throws IOException
165      {
166        if (isStream)
167          {
168            byte[] buf = new byte[1];
169            int in = super.read();
170            if (in == -1)
171              return -1;
172            buf[0] = (byte) in;
173            try
174              {
175                cipher.update(buf, 0, 1, buf, 0);
176              }
177            catch (ShortBufferException shouldNotHappen)
178              {
179                throw new IOException(shouldNotHappen.getMessage());
180              }
181            return buf[0] & 0xFF;
182          }
183    
184        if (outBuffer == null || outOffset == outBuffer.length)
185          {
186            if (eof)
187              return -1;
188            nextBlock ();
189          }
190        return outBuffer [outOffset++] & 0xFF;
191      }
192    
193      /**
194       * Read bytes into an array, returning the number of bytes read or -1
195       * on the end-of-file.
196       *
197       * @param buf The byte array to read into.
198       * @param off The offset in <code>buf</code> to start.
199       * @param len The maximum number of bytes to read.
200       * @return The number of bytes read, or -1 on the end-of-file.
201       * @throws java.io.IOException If an I/O exception occurs.
202       */
203      public synchronized int read(byte[] buf, int off, int len)
204        throws IOException
205      {
206        // CipherInputStream has this wierd implementation where if
207        // the buffer is null, this call is the same as `skip'.
208        if (buf == null)
209          return (int) skip (len);
210    
211        if (isStream)
212          {
213            len = super.read(buf, off, len);
214            if (len > 0)
215              {
216                try
217                  {
218                    cipher.update(buf, off, len, buf, off);
219                  }
220                catch (ShortBufferException shouldNotHappen)
221                  {
222                    IOException ioe = new IOException ("Short buffer for stream cipher -- this should not happen");
223                    ioe.initCause (shouldNotHappen);
224                    throw ioe;
225                  }
226              }
227            return len;
228          }
229    
230        int count = 0;
231        while (count < len)
232          {
233            if (outBuffer == null || outOffset >= outBuffer.length)
234              {
235                if (eof)
236                  {
237                    if (count == 0)
238                      count = -1;
239                    break;
240                  }
241                nextBlock();
242              }
243            int l = Math.min (outBuffer.length - outOffset, len - count);
244            System.arraycopy (outBuffer, outOffset, buf, count+off, l);
245            count += l;
246            outOffset += l;
247          }
248        return count;
249      }
250    
251      /**
252       * Read bytes into an array, returning the number of bytes read or -1
253       * on the end-of-file.
254       *
255       * @param buf The byte arry to read into.
256       * @return The number of bytes read, or -1 on the end-of-file.
257       * @throws java.io.IOException If an I/O exception occurs.
258       */
259      public int read(byte[] buf) throws IOException
260      {
261        return read(buf, 0, buf.length);
262      }
263    
264      /**
265       * Skip a number of bytes. This class only supports skipping as many
266       * bytes as are returned by {@link #available()}, which is the number
267       * of transformed bytes currently in this class's internal buffer.
268       *
269       * @param bytes The number of bytes to skip.
270       * @return The number of bytes skipped.
271       */
272      public long skip(long bytes) throws IOException
273      {
274        if (isStream)
275          {
276            return super.skip(bytes);
277          }
278        long ret = 0;
279        if (bytes > 0 && outBuffer != null && outOffset >= outBuffer.length)
280          {
281            ret = outBuffer.length - outOffset;
282            outOffset = outBuffer.length;
283          }
284        return ret;
285      }
286    
287      /**
288       * Returns whether or not this input stream supports the {@link
289       * #mark(long)} and {@link #reset()} methods; this input stream does
290       * not, however, and invariably returns <code>false</code>.
291       *
292       * @return <code>false</code>
293       */
294      public boolean markSupported()
295      {
296        return false;
297      }
298    
299      /**
300       * Set the mark. This method is unsupported and is empty.
301       *
302       * @param mark Is ignored.
303       */
304      public void mark(int mark)
305      {
306      }
307    
308      /**
309       * Reset to the mark. This method is unsupported and is empty.
310       */
311      public void reset() throws IOException
312      {
313        throw new IOException("reset not supported");
314      }
315    
316      // Own methods.
317      // -------------------------------------------------------------------------
318    
319      // FIXME: I don't fully understand how this class is supposed to work.
320    
321      private void nextBlock() throws IOException
322      {
323        byte[] buf = new byte[cipher.getBlockSize ()];
324        if (Configuration.DEBUG)
325          logger.log (Component.CRYPTO, "getting a new data block");
326    
327        try
328          {
329            outBuffer = null;
330            outOffset = 0;
331            while (outBuffer == null)
332              {
333                int l = in.read (buf);
334                if (Configuration.DEBUG)
335                  logger.log (Component.CRYPTO, "we read {0} bytes",
336                              Integer.valueOf (l));
337                if (l == -1)
338                  {
339                    outBuffer = cipher.doFinal ();
340                    eof = true;
341                    return;
342                  }
343    
344                outOffset = 0;
345                outBuffer = cipher.update (buf, 0, l);
346              }
347          }
348        catch (BadPaddingException bpe)
349          {
350            IOException ioe = new IOException ("bad padding");
351            ioe.initCause (bpe);
352            throw ioe;
353          }
354        catch (IllegalBlockSizeException ibse)
355          {
356            IOException ioe = new IOException ("illegal block size");
357            ioe.initCause (ibse);
358            throw ioe;
359          }
360        finally
361          {
362            if (Configuration.DEBUG)
363              logger.log (Component.CRYPTO,
364                          "decrypted {0} bytes for reading",
365                          Integer.valueOf (outBuffer.length));
366          }
367      }
368    }