8012900: CICO ignores AAD in GCM mode
authorvaleriep
Tue, 08 Oct 2013 11:17:53 -0700
changeset 20752 f0f0acea9113
parent 20751 56898cefcda9
child 20753 95a52f518b5a
8012900: CICO ignores AAD in GCM mode Summary: Change GCM decryption to not return result until tag verification passed Reviewed-by: xuelei
jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java
jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java
jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java
jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java
jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java
jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java
jdk/src/share/classes/com/sun/crypto/provider/GCTR.java
jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java
jdk/src/share/classes/com/sun/crypto/provider/PCBC.java
jdk/src/share/classes/javax/crypto/CipherSpi.java
jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherBlockChaining.java	Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -135,9 +135,10 @@
      * @param plainLen the length of the input data
      * @param cipher the buffer for the result
      * @param cipherOffset the offset in <code>cipher</code>
+     * @return the length of the encrypted data
      */
-    void encrypt(byte[] plain, int plainOffset, int plainLen,
-                 byte[] cipher, int cipherOffset)
+    int encrypt(byte[] plain, int plainOffset, int plainLen,
+                byte[] cipher, int cipherOffset)
     {
         int i;
         int endIndex = plainOffset + plainLen;
@@ -150,6 +151,7 @@
             embeddedCipher.encryptBlock(k, 0, cipher, cipherOffset);
             System.arraycopy(cipher, cipherOffset, r, 0, blockSize);
         }
+        return plainLen;
     }
 
     /**
@@ -174,13 +176,14 @@
      * @param cipherLen the length of the input data
      * @param plain the buffer for the result
      * @param plainOffset the offset in <code>plain</code>
+     * @return the length of the decrypted data
      *
      * @exception IllegalBlockSizeException if input data whose length does
      * not correspond to the embedded cipher's block size is passed to the
      * embedded cipher
      */
-    void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
-                 byte[] plain, int plainOffset)
+    int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+                byte[] plain, int plainOffset)
     {
         int i;
         byte[] cipherOrig=null;
@@ -195,7 +198,6 @@
             // the plaintext result.
             cipherOrig = cipher.clone();
         }
-
         for (; cipherOffset < endIndex;
              cipherOffset += blockSize, plainOffset += blockSize) {
             embeddedCipher.decryptBlock(cipher, cipherOffset, k, 0);
@@ -208,5 +210,6 @@
                 System.arraycopy(cipherOrig, cipherOffset, r, 0, blockSize);
             }
         }
+        return cipherLen;
     }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherCore.java	Tue Oct 08 11:17:53 2013 -0700
@@ -310,49 +310,20 @@
      * @return the required output buffer size (in bytes)
      */
     int getOutputSize(int inputLen) {
-        int totalLen = buffered + inputLen;
-
-        // GCM: this call may be for either update() or doFinal(), so have to
-        // return the larger value of both
-        // Encryption: based on doFinal value: inputLen + tag
-        // Decryption: based on update value: inputLen
-        if (!decrypting && (cipherMode == GCM_MODE)) {
-            return (totalLen + ((GaloisCounterMode) cipher).getTagLen());
-        }
-
-        if (padding == null) {
-            return totalLen;
-        }
-
-        if (decrypting) {
-            return totalLen;
-        }
-
-        if (unitBytes != blockSize) {
-            if (totalLen < diffBlocksize) {
-                return diffBlocksize;
-            } else {
-                return (totalLen + blockSize -
-                        ((totalLen - diffBlocksize) % blockSize));
-            }
-        } else {
-            return totalLen + padding.padLength(totalLen);
-        }
+        // estimate based on the maximum
+        return getOutputSizeByOperation(inputLen, true);
     }
 
     private int getOutputSizeByOperation(int inputLen, boolean isDoFinal) {
-        int totalLen = 0;
+        int totalLen = buffered + inputLen + cipher.getBufferedLength();
         switch (cipherMode) {
         case GCM_MODE:
-            totalLen = buffered + inputLen;
             if (isDoFinal) {
                 int tagLen = ((GaloisCounterMode) cipher).getTagLen();
-                if (decrypting) {
-                    // need to get the actual value from cipher??
-                    // deduct tagLen
+                if (!decrypting) {
+                    totalLen += tagLen;
+                } else {
                     totalLen -= tagLen;
-                } else {
-                    totalLen += tagLen;
                 }
             }
             if (totalLen < 0) {
@@ -360,8 +331,19 @@
             }
             break;
         default:
-             totalLen  = getOutputSize(inputLen);
-             break;
+            if (padding != null && !decrypting) {
+                if (unitBytes != blockSize) {
+                    if (totalLen < diffBlocksize) {
+                        totalLen = diffBlocksize;
+                    } else {
+                        int residue = (totalLen - diffBlocksize) % blockSize;
+                        totalLen += (blockSize - residue);
+                    }
+                } else {
+                    totalLen += padding.padLength(totalLen);
+                }
+            }
+            break;
         }
         return totalLen;
     }
@@ -729,36 +711,52 @@
         len = (len > 0 ? (len - (len%unitBytes)) : 0);
 
         // check output buffer capacity
-        if ((output == null) || ((output.length - outputOffset) < len)) {
+        if ((output == null) ||
+            ((output.length - outputOffset) < len)) {
             throw new ShortBufferException("Output buffer must be "
                                            + "(at least) " + len
                                            + " bytes long");
         }
 
-        if (len != 0) {
-            // there is some work to do
-            byte[] in = new byte[len];
-
-            int inputConsumed = len - buffered;
-            int bufferedConsumed = buffered;
-            if (inputConsumed < 0) {
-                inputConsumed = 0;
-                bufferedConsumed = len;
+        int outLen = 0;
+        if (len != 0) { // there is some work to do
+            if (len <= buffered) {
+                // all to-be-processed data are from 'buffer'
+                if (decrypting) {
+                    outLen = cipher.decrypt(buffer, 0, len, output, outputOffset);
+                } else {
+                    outLen = cipher.encrypt(buffer, 0, len, output, outputOffset);
+                }
+                buffered -= len;
+                if (buffered != 0) {
+                    System.arraycopy(buffer, len, buffer, 0, buffered);
+                }
+            } else { // len > buffered
+                if (buffered == 0) {
+                    // all to-be-processed data are from 'input'
+                    if (decrypting) {
+                        outLen = cipher.decrypt(input, inputOffset, len, output, outputOffset);
+                    } else {
+                        outLen = cipher.encrypt(input, inputOffset, len, output, outputOffset);
+                    }
+                    inputOffset += len;
+                    inputLen -= len;
+                } else {
+                    // assemble the data using both 'buffer' and 'input'
+                    byte[] in = new byte[len];
+                    System.arraycopy(buffer, 0, in, 0, buffered);
+                    int inConsumed = len - buffered;
+                    System.arraycopy(input, inputOffset, in, buffered, inConsumed);
+                    buffered = 0;
+                    inputOffset += inConsumed;
+                    inputLen -= inConsumed;
+                    if (decrypting) {
+                        outLen = cipher.decrypt(in, 0, len, output, outputOffset);
+                    } else {
+                        outLen = cipher.encrypt(in, 0, len, output, outputOffset);
+                    }
+                }
             }
-
-            if (buffered != 0) {
-                System.arraycopy(buffer, 0, in, 0, bufferedConsumed);
-            }
-            if (inputConsumed > 0) {
-                System.arraycopy(input, inputOffset, in,
-                                 bufferedConsumed, inputConsumed);
-            }
-            if (decrypting) {
-                cipher.decrypt(in, 0, len, output, outputOffset);
-            } else {
-                cipher.encrypt(in, 0, len, output, outputOffset);
-            }
-
             // Let's keep track of how many bytes are needed to make
             // the total input length a multiple of blocksize when
             // padding is applied
@@ -770,23 +768,14 @@
                         ((len - diffBlocksize) % blockSize);
                 }
             }
-
-            inputLen -= inputConsumed;
-            inputOffset += inputConsumed;
-            outputOffset += len;
-            buffered -= bufferedConsumed;
-            if (buffered > 0) {
-                System.arraycopy(buffer, bufferedConsumed, buffer, 0,
-                                 buffered);
-            }
         }
-        // left over again
+        // Store remaining input into 'buffer' again
         if (inputLen > 0) {
             System.arraycopy(input, inputOffset, buffer, buffered,
                              inputLen);
+            buffered += inputLen;
         }
-        buffered += inputLen;
-        return len;
+        return outLen;
     }
 
     /**
@@ -881,11 +870,24 @@
                 ("Must use either different key or iv for GCM encryption");
         }
 
-        // calculate the total input length
-        int totalLen = buffered + inputLen;
-        int paddedLen = totalLen;
+        int estOutSize = getOutputSizeByOperation(inputLen, true);
+        // check output buffer capacity.
+        // if we are decrypting with padding applied, we can perform this
+        // check only after we have determined how many padding bytes there
+        // are.
+        int outputCapacity = output.length - outputOffset;
+        int minOutSize = (decrypting? (estOutSize - blockSize):estOutSize);
+        if ((output == null) || (outputCapacity < minOutSize)) {
+            throw new ShortBufferException("Output buffer must be "
+                + "(at least) " + minOutSize + " bytes long");
+        }
+
+        // calculate total input length
+        int len = buffered + inputLen;
+
+        // calculate padding length
+        int totalLen = len + cipher.getBufferedLength();
         int paddingLen = 0;
-
         // will the total input length be a multiple of blockSize?
         if (unitBytes != blockSize) {
             if (totalLen < diffBlocksize) {
@@ -898,40 +900,23 @@
             paddingLen = padding.padLength(totalLen);
         }
 
-        if ((paddingLen > 0) && (paddingLen != blockSize) &&
-            (padding != null) && decrypting) {
+        if (decrypting && (padding != null) &&
+            (paddingLen > 0) && (paddingLen != blockSize)) {
             throw new IllegalBlockSizeException
                 ("Input length must be multiple of " + blockSize +
                  " when decrypting with padded cipher");
         }
 
-        // if encrypting and padding not null, add padding
-        if (!decrypting && padding != null) {
-            paddedLen += paddingLen;
-        }
-
-        // check output buffer capacity.
-        // if we are decrypting with padding applied, we can perform this
-        // check only after we have determined how many padding bytes there
-        // are.
-        if (output == null) {
-            throw new ShortBufferException("Output buffer is null");
-        }
-        int outputCapacity = output.length - outputOffset;
-
-        if (((!decrypting) && (outputCapacity < paddedLen)) ||
-            (decrypting && (outputCapacity < (paddedLen - blockSize)))) {
-            throw new ShortBufferException("Output buffer too short: "
-                                           + outputCapacity + " bytes given, "
-                                           + paddedLen + " bytes needed");
-        }
-
         // prepare the final input avoiding copying if possible
         byte[] finalBuf = input;
         int finalOffset = inputOffset;
+        int finalBufLen = inputLen;
         if ((buffered != 0) || (!decrypting && padding != null)) {
+            if (decrypting || padding == null) {
+                paddingLen = 0;
+            }
+            finalBuf = new byte[len + paddingLen];
             finalOffset = 0;
-            finalBuf = new byte[paddedLen];
             if (buffered != 0) {
                 System.arraycopy(buffer, 0, finalBuf, 0, buffered);
             }
@@ -939,50 +924,50 @@
                 System.arraycopy(input, inputOffset, finalBuf,
                                  buffered, inputLen);
             }
-            if (!decrypting && padding != null) {
-                padding.padWithLen(finalBuf, totalLen, paddingLen);
+            if (paddingLen != 0) {
+                padding.padWithLen(finalBuf, (buffered+inputLen), paddingLen);
             }
+            finalBufLen = finalBuf.length;
         }
-
+        int outLen = 0;
         if (decrypting) {
             // if the size of specified output buffer is less than
             // the length of the cipher text, then the current
             // content of cipher has to be preserved in order for
             // users to retry the call with a larger buffer in the
             // case of ShortBufferException.
-            if (outputCapacity < paddedLen) {
+            if (outputCapacity < estOutSize) {
                 cipher.save();
             }
             // create temporary output buffer so that only "real"
             // data bytes are passed to user's output buffer.
-            byte[] outWithPadding = new byte[totalLen];
-            totalLen = finalNoPadding(finalBuf, finalOffset, outWithPadding,
-                                      0, totalLen);
+            byte[] outWithPadding = new byte[estOutSize];
+            outLen = finalNoPadding(finalBuf, finalOffset, outWithPadding,
+                                    0, finalBufLen);
 
             if (padding != null) {
-                int padStart = padding.unpad(outWithPadding, 0, totalLen);
+                int padStart = padding.unpad(outWithPadding, 0, outLen);
                 if (padStart < 0) {
                     throw new BadPaddingException("Given final block not "
                                                   + "properly padded");
                 }
-                totalLen = padStart;
+                outLen = padStart;
             }
 
-            if ((output.length - outputOffset) < totalLen) {
+            if (outputCapacity < outLen) {
                 // restore so users can retry with a larger buffer
                 cipher.restore();
                 throw new ShortBufferException("Output buffer too short: "
-                                               + (output.length-outputOffset)
-                                               + " bytes given, " + totalLen
+                                               + (outputCapacity)
+                                               + " bytes given, " + outLen
                                                + " bytes needed");
             }
-            for (int i = 0; i < totalLen; i++) {
-                output[outputOffset + i] = outWithPadding[i];
-            }
+            // copy the result into user-supplied output buffer
+            System.arraycopy(outWithPadding, 0, output, outputOffset, outLen);
         } else { // encrypting
             try {
-                totalLen = finalNoPadding(finalBuf, finalOffset, output,
-                                          outputOffset, paddedLen);
+                outLen = finalNoPadding(finalBuf, finalOffset, output,
+                                        outputOffset, finalBufLen);
             } finally {
                 // reset after doFinal() for GCM encryption
                 requireReinit = (cipherMode == GCM_MODE);
@@ -994,12 +979,13 @@
         if (cipherMode != ECB_MODE) {
             cipher.reset();
         }
-        return totalLen;
+        return outLen;
     }
 
     private int finalNoPadding(byte[] in, int inOfs, byte[] out, int outOfs,
                                int len)
-        throws IllegalBlockSizeException, AEADBadTagException {
+        throws IllegalBlockSizeException, AEADBadTagException,
+        ShortBufferException {
 
         if ((cipherMode != GCM_MODE) && (in == null || len == 0)) {
             return 0;
--- a/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CipherFeedback.java	Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -150,9 +150,10 @@
      * @param plainLen the length of the input data
      * @param cipher the buffer for the result
      * @param cipherOffset the offset in <code>cipher</code>
+     * @return the length of the encrypted data
      */
-    void encrypt(byte[] plain, int plainOffset, int plainLen,
-                        byte[] cipher, int cipherOffset)
+    int encrypt(byte[] plain, int plainOffset, int plainLen,
+                byte[] cipher, int cipherOffset)
     {
         int i, len;
         len = blockSize - numBytes;
@@ -194,6 +195,7 @@
                 }
             }
         }
+        return plainLen;
     }
 
     /**
@@ -218,9 +220,10 @@
      * @param cipherLen the length of the input data
      * @param plain the buffer for the result
      * @param plainOffset the offset in <code>plain</code>
+     * @return the length of the decrypted data
      */
-    void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
-                        byte[] plain, int plainOffset)
+    int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+                byte[] plain, int plainOffset)
     {
         int i, len;
         len = blockSize - numBytes;
@@ -268,5 +271,6 @@
                 }
             }
         }
+        return cipherLen;
     }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/CounterMode.java	Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 201313, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -149,9 +149,10 @@
      * @param len the length of the input data
      * @param out the buffer for the result
      * @param outOff the offset in <code>cipher</code>
+     * @return the length of the encrypted data
      */
-    void encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        crypt(in, inOff, len, out, outOff);
+    int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+        return crypt(in, inOff, len, out, outOff);
     }
 
     /**
@@ -176,9 +177,10 @@
      * @param len the length of the input data
      * @param out the buffer for the result
      * @param outOff the offset in <code>plain</code>
+     * @return the length of the decrypted data
      */
-    void decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        crypt(in, inOff, len, out, outOff);
+    int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+        return crypt(in, inOff, len, out, outOff);
     }
 
     /**
@@ -197,7 +199,8 @@
      * keystream generated by encrypting the counter values. Counter values
      * are encrypted on demand.
      */
-    private void crypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+    private int crypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+        int result = len;
         while (len-- > 0) {
             if (used >= blockSize) {
                 embeddedCipher.encryptBlock(counter, 0, encryptedCounter, 0);
@@ -206,5 +209,6 @@
             }
             out[outOff++] = (byte)(in[inOff++] ^ encryptedCounter[used++]);
         }
+        return result;
     }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/ElectronicCodeBook.java	Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -115,14 +115,15 @@
      * @param len the length of the input data
      * @param out the buffer for the result
      * @param outOff the offset in <code>cipher</code>
+     * @return the length of the encrypted data
      */
-    void encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        while (len >= blockSize) {
+    int encrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+        for (int i = len; i >= blockSize; i -= blockSize) {
             embeddedCipher.encryptBlock(in, inOff, out, outOff);
-            len -= blockSize;
             inOff += blockSize;
             outOff += blockSize;
         }
+        return len;
     }
 
     /**
@@ -147,14 +148,14 @@
      * @param len the length of the input data
      * @param out the buffer for the result
      * @param outOff the offset in <code>plain</code>
+     * @return the length of the decrypted data
      */
-    void decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
-        while (len >= blockSize) {
+    int decrypt(byte[] in, int inOff, int len, byte[] out, int outOff) {
+        for (int i = len; i >= blockSize; i -= blockSize) {
             embeddedCipher.decryptBlock(in, inOff, out, outOff);
-            len -= blockSize;
             inOff += blockSize;
             outOff += blockSize;
         }
+        return len;
     }
-
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/FeedbackCipher.java	Tue Oct 08 11:17:53 2013 -0700
@@ -133,9 +133,10 @@
      * @param plainLen the length of the input data
      * @param cipher the buffer for the encryption result
      * @param cipherOffset the offset in <code>cipher</code>
+     * @return the number of bytes placed into <code>cipher</code>
      */
-    abstract void encrypt(byte[] plain, int plainOffset, int plainLen,
-                          byte[] cipher, int cipherOffset);
+    abstract int encrypt(byte[] plain, int plainOffset, int plainLen,
+                         byte[] cipher, int cipherOffset);
     /**
      * Performs encryption operation for the last time.
      *
@@ -154,10 +155,9 @@
      */
      int encryptFinal(byte[] plain, int plainOffset, int plainLen,
                       byte[] cipher, int cipherOffset)
-         throws IllegalBlockSizeException {
-         encrypt(plain, plainOffset, plainLen, cipher, cipherOffset);
-         return plainLen;
-     }
+         throws IllegalBlockSizeException, ShortBufferException {
+         return encrypt(plain, plainOffset, plainLen, cipher, cipherOffset);
+    }
     /**
      * Performs decryption operation.
      *
@@ -174,9 +174,10 @@
      * @param cipherLen the length of the input data
      * @param plain the buffer for the decryption result
      * @param plainOffset the offset in <code>plain</code>
+     * @return the number of bytes placed into <code>plain</code>
      */
-    abstract void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
-                          byte[] plain, int plainOffset);
+    abstract int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+                         byte[] plain, int plainOffset);
 
     /**
      * Performs decryption operation for the last time.
@@ -196,9 +197,9 @@
      */
      int decryptFinal(byte[] cipher, int cipherOffset, int cipherLen,
                       byte[] plain, int plainOffset)
-         throws IllegalBlockSizeException, AEADBadTagException {
-         decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
-         return cipherLen;
+         throws IllegalBlockSizeException, AEADBadTagException,
+         ShortBufferException {
+         return decrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
      }
 
     /**
@@ -228,4 +229,15 @@
     void updateAAD(byte[] src, int offset, int len) {
         throw new IllegalStateException("No AAD accepted");
     }
+
+    /**
+     * @return the number of bytes that are buffered internally inside
+     * this FeedbackCipher instance.
+     * @since 1.8
+     */
+    int getBufferedLength() {
+        // Currently only AEAD cipher impl, e.g. GCM, buffers data
+        // internally during decryption mode
+        return 0;
+    }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/GCTR.java	Tue Oct 08 11:17:53 2013 -0700
@@ -54,7 +54,7 @@
     private byte[] counter;
 
     // needed for save/restore calls
-    private byte[] counterSave;
+    private byte[] counterSave = null;
 
     // NOTE: cipher should already be initialized
     GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
@@ -98,17 +98,16 @@
                 throw new IllegalBlockSizeException("Negative input size!");
             } else if (inLen > 0) {
                 int lastBlockSize = inLen % AES_BLOCK_SIZE;
+                int completeBlkLen = inLen - lastBlockSize;
                 // process the complete blocks first
-                update(in, inOfs, inLen - lastBlockSize, out, outOfs);
+                update(in, inOfs, completeBlkLen, out, outOfs);
                 if (lastBlockSize != 0) {
                     // do the last partial block
                     byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
                     aes.encryptBlock(counter, 0, encryptedCntr, 0);
-
-                    int processed = inLen - lastBlockSize;
                     for (int n = 0; n < lastBlockSize; n++) {
-                        out[outOfs + processed + n] =
-                            (byte) ((in[inOfs + processed + n] ^
+                        out[outOfs + completeBlkLen + n] =
+                            (byte) ((in[inOfs + completeBlkLen + n] ^
                                      encryptedCntr[n]));
                     }
                 }
@@ -120,12 +119,11 @@
     }
 
     /**
-     * Resets the current counter to its initial value.
-     * This is used after the doFinal() is called so this object can be
-     * reused w/o explicit re-initialization.
+     * Resets the content of this object to when it's first constructed.
      */
     void reset() {
         System.arraycopy(icb, 0, counter, 0, icb.length);
+        counterSave = null;
     }
 
     /**
--- a/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Tue Oct 08 11:17:53 2013 -0700
@@ -35,10 +35,12 @@
  * This class represents ciphers in GaloisCounter (GCM) mode.
  *
  * <p>This mode currently should only be used w/ AES cipher.
- * Although no checking is done here, caller should only
- * pass AES Cipher to the constructor.
+ * Although no checking is done, caller should only pass AES
+ * Cipher to the constructor.
  *
- * <p>NOTE: This class does not deal with buffering or padding.
+ * <p>NOTE: Unlike other modes, when used for decryption, this class
+ * will buffer all processed outputs internally and won't return them
+ * until the tag has been successfully verified.
  *
  * @since 1.8
  */
@@ -51,6 +53,9 @@
     private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
     private int sizeOfAAD = 0;
 
+    // buffer for storing input in decryption, not used for encryption
+    private ByteArrayOutputStream ibuffer = null;
+
     // in bytes; need to convert to bits (default value 128) when needed
     private int tagLenBytes = DEFAULT_TAG_LEN;
 
@@ -68,12 +73,14 @@
     // additional variables for save/restore calls
     private byte[] aadBufferSave = null;
     private int sizeOfAADSave = 0;
+    private byte[] ibufferSave = null;
     private int processedSave = 0;
 
     // value must be 16-byte long; used by GCTR and GHASH as well
     static void increment32(byte[] value) {
         if (value.length != AES_BLOCK_SIZE) {
-            throw new RuntimeException("Unexpected counter block length");
+            // should never happen
+            throw new ProviderException("Illegal counter block length");
         }
         // start from last byte and only go over 4 bytes, i.e. total 32 bits
         int n = value.length - 1;
@@ -171,6 +178,9 @@
         if (ghashAllToS != null) ghashAllToS.reset();
         processed = 0;
         sizeOfAAD = 0;
+        if (ibuffer != null) {
+            ibuffer.reset();
+        }
     }
 
     /**
@@ -184,6 +194,9 @@
              null : aadBuffer.toByteArray());
         if (gctrPAndC != null) gctrPAndC.save();
         if (ghashAllToS != null) ghashAllToS.save();
+        if (ibuffer != null) {
+            ibufferSave = ibuffer.toByteArray();
+        }
     }
 
     /**
@@ -198,8 +211,12 @@
                 aadBuffer.write(aadBufferSave, 0, aadBufferSave.length);
             }
         }
-       if (gctrPAndC != null) gctrPAndC.restore();
-       if (ghashAllToS != null) ghashAllToS.restore();
+        if (gctrPAndC != null) gctrPAndC.restore();
+        if (ghashAllToS != null) ghashAllToS.restore();
+        if (ibuffer != null) {
+            ibuffer.reset();
+            ibuffer.write(ibufferSave, 0, ibufferSave.length);
+        }
     }
 
     /**
@@ -261,6 +278,9 @@
         }
         processed = 0;
         sizeOfAAD = 0;
+        if (decrypting) {
+            ibuffer = new ByteArrayOutputStream();
+        }
     }
 
     /**
@@ -299,7 +319,7 @@
 
     // Feed the AAD data to GHASH, pad if necessary
     void processAAD() {
-        if (aadBuffer != null) {
+        if (aadBuffer != null && aadBuffer.size() > 0) {
             byte[] aad = aadBuffer.toByteArray();
             sizeOfAAD = aad.length;
             aadBuffer = null;
@@ -365,13 +385,14 @@
      * @param out the buffer for the result
      * @param outOfs the offset in <code>out</code>
      */
-    void encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+    int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
         processAAD();
         if (len > 0) {
             gctrPAndC.update(in, inOfs, len, out, outOfs);
             processed += len;
             ghashAllToS.update(out, outOfs, len);
         }
+        return len;
     }
 
     /**
@@ -387,28 +408,28 @@
      * @param outOfs the offset in <code>out</code>
      * @return the number of bytes placed into the <code>out</code> buffer
      */
-     int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
-         throws IllegalBlockSizeException {
-         if (out.length - outOfs < (len + tagLenBytes)) {
-             throw new RuntimeException("Output buffer too small");
-         }
+    int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
+        throws IllegalBlockSizeException, ShortBufferException {
+        if (out.length - outOfs < (len + tagLenBytes)) {
+            throw new ShortBufferException("Output buffer too small");
+        }
+
+        processAAD();
+        if (len > 0) {
+            doLastBlock(in, inOfs, len, out, outOfs, true);
+        }
 
-         processAAD();
-         if (len > 0) {
-             //ByteUtil.dumpArray(Arrays.copyOfRange(in, inOfs, inOfs + len));
-             doLastBlock(in, inOfs, len, out, outOfs, true);
-         }
+        byte[] lengthBlock =
+            getLengthBlock(sizeOfAAD*8, processed*8);
+        ghashAllToS.update(lengthBlock);
+        byte[] s = ghashAllToS.digest();
+        byte[] sOut = new byte[s.length];
+        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+        gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
 
-         byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
-         ghashAllToS.update(lengthBlock);
-         byte[] s = ghashAllToS.digest();
-         byte[] sOut = new byte[s.length];
-         GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-         gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
-
-         System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
-         return (len + tagLenBytes);
-     }
+        System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes);
+        return (len + tagLenBytes);
+    }
 
     /**
      * Performs decryption operation.
@@ -432,14 +453,16 @@
      * @param out the buffer for the result
      * @param outOfs the offset in <code>out</code>
      */
-    void decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
+    int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) {
         processAAD();
 
-        if (len > 0) { // must be at least AES_BLOCK_SIZE bytes long
-            gctrPAndC.update(in, inOfs, len, out, outOfs);
-            processed += len;
-            ghashAllToS.update(in, inOfs, len);
+        if (len > 0) {
+            // store internally until decryptFinal is called because
+            // spec mentioned that only return recovered data after tag
+            // is successfully verified
+            ibuffer.write(in, inOfs, len);
         }
+        return 0;
     }
 
     /**
@@ -458,44 +481,62 @@
      * @param outOfs the offset in <code>plain</code>
      * @return the number of bytes placed into the <code>out</code> buffer
      */
-     int decryptFinal(byte[] in, int inOfs, int len,
-                      byte[] out, int outOfs)
-         throws IllegalBlockSizeException, AEADBadTagException {
-         if (len < tagLenBytes) {
-             throw new RuntimeException("Input buffer too short - need tag");
-         }
-         if (out.length - outOfs < (len - tagLenBytes)) {
-             throw new RuntimeException("Output buffer too small");
-         }
-         processAAD();
+    int decryptFinal(byte[] in, int inOfs, int len,
+                     byte[] out, int outOfs)
+        throws IllegalBlockSizeException, AEADBadTagException,
+        ShortBufferException {
+        if (len < tagLenBytes) {
+            throw new AEADBadTagException("Input too short - need tag");
+        }
+        if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
+            throw new ShortBufferException("Output buffer too small");
+        }
+        processAAD();
+        if (len != 0) {
+            ibuffer.write(in, inOfs, len);
+        }
 
-         int processedOld = processed;
-         byte[] tag = new byte[tagLenBytes];
-         // get the trailing tag bytes from 'in'
-         System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
-         len -= tagLenBytes;
+        // refresh 'in' to all buffered-up bytes
+        in = ibuffer.toByteArray();
+        inOfs = 0;
+        len = in.length;
+        ibuffer.reset();
 
-         if (len > 0) {
-             doLastBlock(in, inOfs, len, out, outOfs, false);
-         }
+        byte[] tag = new byte[tagLenBytes];
+        // get the trailing tag bytes from 'in'
+        System.arraycopy(in, len - tagLenBytes, tag, 0, tagLenBytes);
+        len -= tagLenBytes;
 
-         byte[] lengthBlock = getLengthBlock(sizeOfAAD*8, processed*8);
-         ghashAllToS.update(lengthBlock);
+        if (len > 0) {
+            doLastBlock(in, inOfs, len, out, outOfs, false);
+        }
 
-         byte[] s = ghashAllToS.digest();
-         byte[] sOut = new byte[s.length];
-         GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
-         gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
-         for (int i = 0; i < tagLenBytes; i++) {
-             if (tag[i] != sOut[i]) {
-                 throw new AEADBadTagException("Tag mismatch!");
-             }
-         }
-         return len;
-     }
+        byte[] lengthBlock =
+            getLengthBlock(sizeOfAAD*8, processed*8);
+        ghashAllToS.update(lengthBlock);
+
+        byte[] s = ghashAllToS.digest();
+        byte[] sOut = new byte[s.length];
+        GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock);
+        gctrForSToTag.doFinal(s, 0, s.length, sOut, 0);
+        for (int i = 0; i < tagLenBytes; i++) {
+            if (tag[i] != sOut[i]) {
+                throw new AEADBadTagException("Tag mismatch!");
+            }
+        }
+        return len;
+    }
 
     // return tag length in bytes
     int getTagLen() {
         return this.tagLenBytes;
     }
+
+    int getBufferedLength() {
+        if (ibuffer == null) {
+            return 0;
+        } else {
+            return ibuffer.size();
+        }
+    }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/OutputFeedback.java	Tue Oct 08 11:17:53 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -149,9 +149,10 @@
      * @param plainLen the length of the input data
      * @param cipher the buffer for the result
      * @param cipherOffset the offset in <code>cipher</code>
+     * @return the length of the encrypted data
      */
-    void encrypt(byte[] plain, int plainOffset, int plainLen,
-                        byte[] cipher, int cipherOffset)
+    int encrypt(byte[] plain, int plainOffset, int plainLen,
+                byte[] cipher, int cipherOffset)
     {
         int i;
         int len = blockSize - numBytes;
@@ -195,6 +196,7 @@
                 System.arraycopy(k, 0, register, len, numBytes);
             }
         }
+        return plainLen;
     }
 
     /**
@@ -219,11 +221,12 @@
      * @param cipherLen the length of the input data
      * @param plain the buffer for the result
      * @param plainOffset the offset in <code>plain</code>
+     * @return the length of the decrypted data
      */
-    void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+    int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
                         byte[] plain, int plainOffset)
     {
         // OFB encrypt and decrypt are identical
-        encrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
+        return encrypt(cipher, cipherOffset, cipherLen, plain, plainOffset);
     }
 }
--- a/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/com/sun/crypto/provider/PCBC.java	Tue Oct 08 11:17:53 2013 -0700
@@ -136,8 +136,8 @@
      * @param cipher the buffer for the result
      * @param cipherOffset the offset in <code>cipher</code>
      */
-    void encrypt(byte[] plain, int plainOffset, int plainLen,
-                        byte[] cipher, int cipherOffset)
+    int encrypt(byte[] plain, int plainOffset, int plainLen,
+                byte[] cipher, int cipherOffset)
     {
         int i;
         int endIndex = plainOffset + plainLen;
@@ -152,6 +152,7 @@
                 k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]);
             }
         }
+        return plainLen;
     }
 
     /**
@@ -177,8 +178,8 @@
      * @param plain the buffer for the result
      * @param plainOffset the offset in <code>plain</code>
      */
-    void decrypt(byte[] cipher, int cipherOffset, int cipherLen,
-                        byte[] plain, int plainOffset)
+    int decrypt(byte[] cipher, int cipherOffset, int cipherLen,
+                byte[] plain, int plainOffset)
     {
         int i;
         int endIndex = cipherOffset + cipherLen;
@@ -194,5 +195,6 @@
                 k[i] = (byte)(plain[i+plainOffset] ^ cipher[i+cipherOffset]);
             }
         }
+        return cipherLen;
     }
 }
--- a/jdk/src/share/classes/javax/crypto/CipherSpi.java	Tue Oct 08 11:07:31 2013 -0700
+++ b/jdk/src/share/classes/javax/crypto/CipherSpi.java	Tue Oct 08 11:17:53 2013 -0700
@@ -786,7 +786,9 @@
             int total = 0;
             do {
                 int chunk = Math.min(inLen, inArray.length);
-                input.get(inArray, 0, chunk);
+                if (chunk > 0) {
+                    input.get(inArray, 0, chunk);
+                }
                 int n;
                 if (isUpdate || (inLen != chunk)) {
                     n = engineUpdate(inArray, 0, chunk, outArray, outOfs);
@@ -814,8 +816,9 @@
             int total = 0;
             boolean resized = false;
             do {
-                int chunk = Math.min(inLen, outSize);
-                if ((a1 == false) && (resized == false)) {
+                int chunk =
+                    Math.min(inLen, (outSize == 0? inArray.length : outSize));
+                if (!a1 && !resized && chunk > 0) {
                     input.get(inArray, 0, chunk);
                     inOfs = 0;
                 }
@@ -829,8 +832,10 @@
                     resized = false;
                     inOfs += chunk;
                     inLen -= chunk;
-                    output.put(outArray, 0, n);
-                    total += n;
+                    if (n > 0) {
+                        output.put(outArray, 0, n);
+                        total += n;
+                    }
                 } catch (ShortBufferException e) {
                     if (resized) {
                         // we just resized the output buffer, but it still
@@ -840,11 +845,13 @@
                     }
                     // output buffer is too small, realloc and try again
                     resized = true;
-                    int newOut = engineGetOutputSize(chunk);
-                    outArray = new byte[newOut];
+                    outSize = engineGetOutputSize(chunk);
+                    outArray = new byte[outSize];
                 }
             } while (inLen > 0);
-            input.position(inLimit);
+            if (a1) {
+                input.position(inLimit);
+            }
             return total;
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/com/sun/crypto/provider/Cipher/AES/TestCICOWithGCMAndAAD.java	Tue Oct 08 11:17:53 2013 -0700
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8012900
+ * @library ../UTIL
+ * @build TestUtil
+ * @run main TestCICOWithGCMAndAAD
+ * @summary Test CipherInputStream/OutputStream with AES GCM mode with AAD.
+ * @author Valerie Peng
+ */
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import javax.crypto.*;
+
+public class TestCICOWithGCMAndAAD {
+    public static void main(String[] args) throws Exception {
+        //init Secret Key
+        KeyGenerator kg = KeyGenerator.getInstance("AES", "SunJCE");
+        kg.init(128);
+        SecretKey key = kg.generateKey();
+
+        //Do initialization of the plainText
+        byte[] plainText = new byte[700];
+        Random rdm = new Random();
+        rdm.nextBytes(plainText);
+
+        byte[] aad = new byte[128];
+        rdm.nextBytes(aad);
+        byte[] aad2 = aad.clone();
+        aad2[50]++;
+
+        Cipher encCipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
+        encCipher.init(Cipher.ENCRYPT_MODE, key);
+        encCipher.updateAAD(aad);
+        Cipher decCipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
+        decCipher.init(Cipher.DECRYPT_MODE, key, encCipher.getParameters());
+        decCipher.updateAAD(aad);
+
+        byte[] recovered = test(encCipher, decCipher, plainText);
+        if (!Arrays.equals(plainText, recovered)) {
+            throw new Exception("sameAAD: diff check failed!");
+        } else System.out.println("sameAAD: passed");
+
+        encCipher.init(Cipher.ENCRYPT_MODE, key);
+        encCipher.updateAAD(aad2);
+        recovered = test(encCipher, decCipher, plainText);
+        if (recovered != null && recovered.length != 0) {
+            throw new Exception("diffAAD: no data should be returned!");
+        } else System.out.println("diffAAD: passed");
+   }
+
+   private static byte[] test(Cipher encCipher, Cipher decCipher, byte[] plainText)
+            throws Exception {
+        //init cipher streams
+        ByteArrayInputStream baInput = new ByteArrayInputStream(plainText);
+        CipherInputStream ciInput = new CipherInputStream(baInput, encCipher);
+        ByteArrayOutputStream baOutput = new ByteArrayOutputStream();
+        CipherOutputStream ciOutput = new CipherOutputStream(baOutput, decCipher);
+
+        //do test
+        byte[] buffer = new byte[200];
+        int len = ciInput.read(buffer);
+        System.out.println("read " + len + " bytes from input buffer");
+
+        while (len != -1) {
+            ciOutput.write(buffer, 0, len);
+            System.out.println("wite " + len + " bytes to output buffer");
+            len = ciInput.read(buffer);
+            if (len != -1) {
+                System.out.println("read " + len + " bytes from input buffer");
+            } else {
+                System.out.println("finished reading");
+            }
+        }
+
+        ciOutput.flush();
+        ciInput.close();
+        ciOutput.close();
+
+        return baOutput.toByteArray();
+    }
+}