Source for javax.crypto.CipherInputStream

   1: /* CipherInputStream.java -- Filters input through a cipher.
   2:    Copyright (C) 2004  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.crypto;
  40: 
  41: import gnu.classpath.Configuration;
  42: import gnu.classpath.debug.Component;
  43: import gnu.classpath.debug.SystemLogger;
  44: 
  45: import java.io.FilterInputStream;
  46: import java.io.IOException;
  47: import java.io.InputStream;
  48: 
  49: import java.util.logging.Logger;
  50: 
  51: /**
  52:  * This is an {@link java.io.InputStream} that filters its data
  53:  * through a {@link Cipher} before returning it. The <code>Cipher</code>
  54:  * argument must have been initialized before it is passed to the
  55:  * constructor.
  56:  *
  57:  * @author Casey Marshall (csm@gnu.org)
  58:  */
  59: public class CipherInputStream extends FilterInputStream
  60: {
  61: 
  62:   // Constants and variables.
  63:   // ------------------------------------------------------------------------
  64: 
  65:   private static final Logger logger = SystemLogger.SYSTEM;
  66: 
  67:   /**
  68:    * The underlying {@link Cipher} instance.
  69:    */
  70:   private final Cipher cipher;
  71: 
  72:   /**
  73:    * Data that has been transformed but not read.
  74:    */
  75:   private byte[] outBuffer;
  76: 
  77:   /**
  78:    * The offset into {@link #outBuffer} where valid data starts.
  79:    */
  80:   private int outOffset;
  81: 
  82:   /**
  83:    * We set this when the cipher block size is 1, meaning that we can
  84:    * transform any amount of data.
  85:    */
  86:   private final boolean isStream;
  87: 
  88:   /**
  89:    * Whether or not we've reached the end of the stream.
  90:    */
  91:   private boolean eof;
  92: 
  93:   // Constructors.
  94:   // ------------------------------------------------------------------------
  95: 
  96:   /**
  97:    * Creates a new input stream with a source input stream and cipher.
  98:    *
  99:    * @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: }