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 java.io.FilterInputStream;
  42: import java.io.IOException;
  43: import java.io.InputStream;
  44: 
  45: /**
  46:  * This is an {@link java.io.InputStream} that filters its data
  47:  * through a {@link Cipher} before returning it. The <code>Cipher</code>
  48:  * argument must have been initialized before it is passed to the
  49:  * constructor.
  50:  *
  51:  * @author Casey Marshall (csm@gnu.org)
  52:  */
  53: public class CipherInputStream extends FilterInputStream
  54: {
  55: 
  56:   // Constants and variables.
  57:   // ------------------------------------------------------------------------
  58: 
  59:   /**
  60:    * The underlying {@link Cipher} instance.
  61:    */
  62:   private Cipher cipher;
  63: 
  64:   /**
  65:    * Data that has been transformed but not read.
  66:    */
  67:   private byte[] outBuffer;
  68: 
  69:   /**
  70:    * The offset into {@link #outBuffer} where valid data starts.
  71:    */
  72:   private int outOffset;
  73: 
  74:   /**
  75:    * The number of valid bytes in the {@link #outBuffer}.
  76:    */
  77:   private int outLength;
  78: 
  79:   /**
  80:    * Byte buffer that is filled with raw data from the underlying input
  81:    * stream.
  82:    */
  83:   private byte[][] inBuffer;
  84: 
  85:   /**
  86:    * The amount of bytes in inBuffer[0] that may be input to the cipher.
  87:    */
  88:   private int inLength;
  89: 
  90:   /**
  91:    * We set this when the cipher block size is 1, meaning that we can
  92:    * transform any amount of data.
  93:    */
  94:   private boolean isStream;
  95: 
  96:   private static final int VIRGIN = 0;  // I am born.
  97:   private static final int LIVING = 1;  // I am nailed to the hull.
  98:   private static final int DYING  = 2;  // I am eaten by sharks.
  99:   private static final int DEAD   = 3;
 100:   private int state;
 101: 
 102:   // Constructors.
 103:   // ------------------------------------------------------------------------
 104: 
 105:   /**
 106:    * Creates a new input stream with a source input stream and cipher.
 107:    *
 108:    * @param in     The underlying input stream.
 109:    * @param cipher The cipher to filter data through.
 110:    */
 111:   public CipherInputStream(InputStream in, Cipher cipher)
 112:   {
 113:     this(in);
 114:     this.cipher = cipher;
 115:     if (!(isStream = cipher.getBlockSize() == 1))
 116:       {
 117:         inBuffer = new byte[2][];
 118:         inBuffer[0] = new byte[cipher.getBlockSize()];
 119:         inBuffer[1] = new byte[cipher.getBlockSize()];
 120:         inLength = 0;
 121:         outBuffer = new byte[cipher.getBlockSize()];
 122:         outOffset = outLength = 0;
 123:         state = VIRGIN;
 124:       }
 125:   }
 126: 
 127:   /**
 128:    * Creates a new input stream without a cipher. This constructor is
 129:    * <code>protected</code> because this class does not work without an
 130:    * underlying cipher.
 131:    *
 132:    * @param in The underlying input stream.
 133:    */
 134:   protected CipherInputStream(InputStream in)
 135:   {
 136:     super(in);
 137:   }
 138: 
 139:   // Instance methods overriding java.io.FilterInputStream.
 140:   // ------------------------------------------------------------------------
 141: 
 142:   /**
 143:    * Returns the number of bytes available without blocking. The value
 144:    * returned by this method is never greater than the underlying
 145:    * cipher's block size.
 146:    *
 147:    * @return The number of bytes immediately available.
 148:    * @throws java.io.IOException If an I/O exception occurs.
 149:    */
 150:   public int available() throws IOException
 151:   {
 152:     if (isStream)
 153:       return super.available();
 154:     return outLength - outOffset;
 155:   }
 156: 
 157:   /**
 158:    * Close this input stream. This method merely calls the {@link
 159:    * java.io.InputStream#close()} method of the underlying input stream.
 160:    *
 161:    * @throws java.io.IOException If an I/O exception occurs.
 162:    */
 163:   public void close() throws IOException
 164:   {
 165:     super.close();
 166:   }
 167: 
 168:   /**
 169:    * Read a single byte from this input stream; returns -1 on the
 170:    * end-of-file.
 171:    *
 172:    * @return The byte read, or -1 if there are no more bytes.
 173:    * @throws java.io.IOExcpetion If an I/O exception occurs.
 174:    */
 175:   public int read() throws IOException
 176:   {
 177:     if (isStream)
 178:       {
 179:         byte[] buf = new byte[1];
 180:         int in = super.read();
 181:         if (in == -1)
 182:           return -1;
 183:         buf[0] = (byte) in;
 184:         try
 185:           {
 186:             cipher.update(buf, 0, 1, buf, 0);
 187:           }
 188:         catch (ShortBufferException shouldNotHappen)
 189:           {
 190:             throw new IOException(shouldNotHappen.getMessage());
 191:           }
 192:         return buf[0] & 0xFF;
 193:       }
 194:     if (state == DEAD) return -1;
 195:     if (available() == 0) nextBlock();
 196:     if (state == DEAD) return -1;
 197:     return outBuffer[outOffset++] & 0xFF;
 198:   }
 199: 
 200:   /**
 201:    * Read bytes into an array, returning the number of bytes read or -1
 202:    * on the end-of-file.
 203:    *
 204:    * @param buf The byte array to read into.
 205:    * @param off The offset in <code>buf</code> to start.
 206:    * @param len The maximum number of bytes to read.
 207:    * @return The number of bytes read, or -1 on the end-of-file.
 208:    * @throws java.io.IOException If an I/O exception occurs.
 209:    */
 210:   public int read(byte[] buf, int off, int len) throws IOException
 211:   {
 212:     if (isStream)
 213:       {
 214:         len = super.read(buf, off, len);
 215:         try
 216:           {
 217:             cipher.update(buf, off, len, buf, off);
 218:           }
 219:         catch (ShortBufferException shouldNotHappen)
 220:           {
 221:             throw new IOException(shouldNotHappen.getMessage());
 222:           }
 223:         return len;
 224:       }
 225: 
 226:     int count = 0;
 227:     while (count < len)
 228:       {
 229:         if (available() == 0)
 230:           nextBlock();
 231:         if (state == DEAD)
 232:           {
 233:             if (count > 0) return count;
 234:             else return -1;
 235:           }
 236:         int l = Math.min(available(), len - count);
 237:         System.arraycopy(outBuffer, outOffset, buf, count+off, l);
 238:         count += l;
 239:         outOffset = outLength = 0;
 240:       }
 241:     return count;
 242:   }
 243: 
 244:   /**
 245:    * Read bytes into an array, returning the number of bytes read or -1
 246:    * on the end-of-file.
 247:    *
 248:    * @param buf The byte arry to read into.
 249:    * @return The number of bytes read, or -1 on the end-of-file.
 250:    * @throws java.io.IOException If an I/O exception occurs.
 251:    */
 252:   public int read(byte[] buf) throws IOException
 253:   {
 254:     return read(buf, 0, buf.length);
 255:   }
 256: 
 257:   /**
 258:    * Skip a number of bytes. This class only supports skipping as many
 259:    * bytes as are returned by {@link #available()}, which is the number
 260:    * of transformed bytes currently in this class's internal buffer.
 261:    *
 262:    * @param bytes The number of bytes to skip.
 263:    * @return The number of bytes skipped.
 264:    */
 265:   public long skip(long bytes) throws IOException
 266:   {
 267:     if (isStream)
 268:       {
 269:         return super.skip(bytes);
 270:       }
 271:     long ret = 0;
 272:     if (bytes > 0 && available() > 0)
 273:       {
 274:         ret = available();
 275:         outOffset = outLength = 0;
 276:       }
 277:     return ret;
 278:   }
 279: 
 280:   /**
 281:    * Returns whether or not this input stream supports the {@link
 282:    * #mark(long)} and {@link #reset()} methods; this input stream does
 283:    * not, however, and invariably returns <code>false</code>.
 284:    *
 285:    * @return <code>false</code>
 286:    */
 287:   public boolean markSupported()
 288:   {
 289:     return false;
 290:   }
 291: 
 292:   /**
 293:    * Set the mark. This method is unsupported and is empty.
 294:    *
 295:    * @param mark Is ignored.
 296:    */
 297:   public void mark(int mark)
 298:   {
 299:   }
 300: 
 301:   /**
 302:    * Reset to the mark. This method is unsupported and is empty.
 303:    */
 304:   public void reset() throws IOException
 305:   {
 306:     throw new IOException("reset not supported");
 307:   }
 308: 
 309:   // Own methods.
 310:   // -------------------------------------------------------------------------
 311: 
 312:   private void nextBlock() throws IOException
 313:   {
 314:     byte[] temp = inBuffer[0];
 315:     inBuffer[0] = inBuffer[1];
 316:     inBuffer[1] = temp;
 317:     int count = 0;
 318:     boolean eof = false;
 319: 
 320:     if (state == VIRGIN || state == LIVING)
 321:       {
 322:         do
 323:           {
 324:             int l = in.read(inBuffer[1], count, inBuffer[1].length - count);
 325:             if (l == -1)
 326:               {
 327:                 eof = true;
 328:                 break;
 329:               }
 330:             count += l;
 331:           }
 332:         while (count < inBuffer[1].length);
 333:       }
 334: 
 335:     try
 336:       {
 337:         switch (state)
 338:           {
 339:           case VIRGIN:
 340:             state = LIVING;
 341:             nextBlock();
 342:             break;
 343:           case LIVING:
 344:             if (eof)
 345:               {
 346:                 if (count > 0)
 347:                   {
 348:                     outOffset = cipher.update(inBuffer[0], 0, inLength, outBuffer, 0);
 349:                     state = DYING;
 350:                   }
 351:                 else
 352:                   {
 353:                     outOffset = cipher.doFinal(inBuffer[0], 0, inLength, outBuffer, 0);
 354:                     state = DEAD;
 355:                   }
 356:               }
 357:             else
 358:               {
 359:                 outOffset = cipher.update(inBuffer[0], 0, inLength, outBuffer, 0);
 360:               }
 361:             break;
 362:           case DYING:
 363:             outOffset = cipher.doFinal(inBuffer[0], 0, inLength, outBuffer, 0);
 364:             state = DEAD;
 365:             break;
 366:           case DEAD:
 367:           }
 368:       }
 369:     catch (ShortBufferException sbe)
 370:       {
 371:         throw new IOException(sbe.toString());
 372:       }
 373:     catch (BadPaddingException bpe)
 374:       {
 375:         throw new IOException(bpe.toString());
 376:       }
 377:     catch (IllegalBlockSizeException ibse)
 378:       {
 379:         throw new IOException(ibse.toString());
 380:       }
 381:     inLength = count;
 382:   }
 383: }