jdk/src/share/classes/sun/security/ssl/CipherBox.java
changeset 7039 6464c8e62a18
parent 5506 202f599c92aa
child 10915 1e20964cebf3
child 10787 717ef54d7dd3
--- a/jdk/src/share/classes/sun/security/ssl/CipherBox.java	Fri Oct 29 12:35:07 2010 +0200
+++ b/jdk/src/share/classes/sun/security/ssl/CipherBox.java	Sat Oct 30 18:39:17 2010 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2010, 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
@@ -28,6 +28,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.util.Hashtable;
 
 import java.security.*;
 import javax.crypto.*;
@@ -50,6 +51,37 @@
  * Individual instances are obtained by calling the static method
  * newCipherBox(), which should only be invoked by BulkCipher.newCipher().
  *
+ * In RFC 2246, with bock ciphers in CBC mode, the Initialization
+ * Vector (IV) for the first record is generated with the other keys
+ * and secrets when the security parameters are set.  The IV for
+ * subsequent records is the last ciphertext block from the previous
+ * record.
+ *
+ * In RFC 4346, the implicit Initialization Vector (IV) is replaced
+ * with an explicit IV to protect against CBC attacks.  RFC 4346
+ * recommends two algorithms used to generated the per-record IV.
+ * The implementation uses the algorithm (2)(b), as described at
+ * section 6.2.3.2 of RFC 4346.
+ *
+ * The usage of IV in CBC block cipher can be illustrated in
+ * the following diagrams.
+ *
+ *   (random)
+ *        R         P1                    IV        C1
+ *        |          |                     |         |
+ *  SIV---+    |-----+    |-...            |-----    |------
+ *        |    |     |    |                |    |    |     |
+ *     +----+  |  +----+  |             +----+  |  +----+  |
+ *     | Ek |  |  + Ek +  |             | Dk |  |  | Dk |  |
+ *     +----+  |  +----+  |             +----+  |  +----+  |
+ *        |    |     |    |                |    |    |     |
+ *        |----|     |----|           SIV--+    |----|     |-...
+ *        |          |                     |       |
+ *       IV         C1                     R      P1
+ *                                     (discard)
+ *
+ *       CBC Encryption                    CBC Decryption
+ *
  * NOTE that any ciphering involved in key exchange (e.g. with RSA) is
  * handled separately.
  *
@@ -76,6 +108,21 @@
     private int blockSize;
 
     /**
+     * secure random
+     */
+    private SecureRandom random;
+
+    /**
+     * Fixed masks of various block size, as the initial decryption IVs
+     * for TLS 1.1 or later.
+     *
+     * For performance, we do not use random IVs. As the initial decryption
+     * IVs will be discarded by TLS decryption processes, so the fixed masks
+     * do not hurt cryptographic strength.
+     */
+    private static Hashtable<Integer, IvParameterSpec> masks;
+
+    /**
      * NULL cipherbox. Identity operation, no encryption.
      */
     private CipherBox() {
@@ -90,14 +137,37 @@
      * implementation could be found.
      */
     private CipherBox(ProtocolVersion protocolVersion, BulkCipher bulkCipher,
-            SecretKey key,  IvParameterSpec iv, boolean encrypt)
-            throws NoSuchAlgorithmException {
+            SecretKey key, IvParameterSpec iv, SecureRandom random,
+            boolean encrypt) throws NoSuchAlgorithmException {
         try {
             this.protocolVersion = protocolVersion;
             this.cipher = JsseJce.getCipher(bulkCipher.transformation);
             int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
-            cipher.init(mode, key, iv);
-            // do not call getBlockSize until after init()
+
+            if (random == null) {
+                random = JsseJce.getSecureRandom();
+            }
+            this.random = random;
+
+            /*
+             * RFC 4346 recommends two algorithms used to generated the
+             * per-record IV. The implementation uses the algorithm (2)(b),
+             * as described at section 6.2.3.2 of RFC 4346.
+             *
+             * As we don't care about the initial IV value for TLS 1.1 or
+             * later, so if the "iv" parameter is null, we use the default
+             * value generated by Cipher.init() for encryption, and a fixed
+             * mask for decryption.
+             */
+            if (iv == null && bulkCipher.ivSize != 0 &&
+                    mode == Cipher.DECRYPT_MODE &&
+                    protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                iv = getFixedMask(bulkCipher.ivSize);
+            }
+
+            cipher.init(mode, key, iv, random);
+
+            // Do not call getBlockSize until after init()
             // otherwise we would disrupt JCE delayed provider selection
             blockSize = cipher.getBlockSize();
             // some providers implement getBlockSize() incorrectly
@@ -119,19 +189,37 @@
      * Factory method to obtain a new CipherBox object.
      */
     static CipherBox newCipherBox(ProtocolVersion version, BulkCipher cipher,
-            SecretKey key, IvParameterSpec iv, boolean encrypt)
-            throws NoSuchAlgorithmException {
+            SecretKey key, IvParameterSpec iv, SecureRandom random,
+            boolean encrypt) throws NoSuchAlgorithmException {
         if (cipher.allowed == false) {
             throw new NoSuchAlgorithmException("Unsupported cipher " + cipher);
         }
+
         if (cipher == B_NULL) {
             return NULL;
         } else {
-            return new CipherBox(version, cipher, key, iv, encrypt);
+            return new CipherBox(version, cipher, key, iv, random, encrypt);
         }
     }
 
     /*
+     * Get a fixed mask, as the initial decryption IVs for TLS 1.1 or later.
+     */
+    private static IvParameterSpec getFixedMask(int ivSize) {
+        if (masks == null) {
+            masks = new Hashtable<Integer, IvParameterSpec>(5);
+        }
+
+        IvParameterSpec iv = masks.get(ivSize);
+        if (iv == null) {
+            iv = new IvParameterSpec(new byte[ivSize]);
+            masks.put(ivSize, iv);
+        }
+
+        return iv;
+    }
+
+    /*
      * Encrypts a block of data, returning the size of the
      * resulting block.
      */
@@ -139,8 +227,26 @@
         if (cipher == null) {
             return len;
         }
+
         try {
             if (blockSize != 0) {
+                // TLSv1.1 needs a IV block
+                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                    // generate a random number
+                    byte[] prefix = new byte[blockSize];
+                    random.nextBytes(prefix);
+
+                    // move forward the plaintext
+                    System.arraycopy(buf, offset,
+                                     buf, offset + prefix.length, len);
+
+                    // prefix the plaintext
+                    System.arraycopy(prefix, 0,
+                                     buf, offset, prefix.length);
+
+                    len += prefix.length;
+                }
+
                 len = addPadding(buf, offset, len, blockSize);
             }
             if (debug != null && Debug.isOn("plaintext")) {
@@ -189,6 +295,34 @@
             int pos = bb.position();
 
             if (blockSize != 0) {
+                // TLSv1.1 needs a IV block
+                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                    // generate a random number
+                    byte[] prefix = new byte[blockSize];
+                    random.nextBytes(prefix);
+
+                    // move forward the plaintext
+                    byte[] buf = null;
+                    int limit = bb.limit();
+                    if (bb.hasArray()) {
+                        buf = bb.array();
+                        System.arraycopy(buf, pos,
+                                buf, pos + prefix.length, limit - pos);
+                        bb.limit(limit + prefix.length);
+                    } else {
+                        buf = new byte[limit - pos];
+                        bb.get(buf, 0, limit - pos);
+                        bb.position(pos + prefix.length);
+                        bb.limit(limit + prefix.length);
+                        bb.put(buf);
+                    }
+                    bb.position(pos);
+
+                    // prefix the plaintext
+                    bb.put(prefix);
+                    bb.position(pos);
+                }
+
                 // addPadding adjusts pos/limit
                 len = addPadding(bb, blockSize);
                 bb.position(pos);
@@ -236,11 +370,25 @@
     /*
      * Decrypts a block of data, returning the size of the
      * resulting block if padding was required.
+     *
+     * For SSLv3 and TLSv1.0, with block ciphers in CBC mode the
+     * Initialization Vector (IV) for the first record is generated by
+     * the handshake protocol, the IV for subsequent records is the
+     * last ciphertext block from the previous record.
+     *
+     * From TLSv1.1, the implicit IV is replaced with an explicit IV to
+     * protect against CBC attacks.
+     *
+     * Differentiating between bad_record_mac and decryption_failed alerts
+     * may permit certain attacks against CBC mode. It is preferable to
+     * uniformly use the bad_record_mac alert to hide the specific type of
+     * the error.
      */
     int decrypt(byte[] buf, int offset, int len) throws BadPaddingException {
         if (cipher == null) {
             return len;
         }
+
         try {
             int newLen = cipher.update(buf, offset, len, buf, offset);
             if (newLen != len) {
@@ -263,6 +411,18 @@
             if (blockSize != 0) {
                 newLen = removePadding(buf, offset, newLen,
                              blockSize, protocolVersion);
+
+                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                    if (newLen < blockSize) {
+                        throw new BadPaddingException("invalid explicit IV");
+                    }
+
+                    // discards the first cipher block, the IV component.
+                    System.arraycopy(buf, offset + blockSize,
+                                     buf, offset, newLen - blockSize);
+
+                    newLen -= blockSize;
+                }
             }
             return newLen;
         } catch (ShortBufferException e) {
@@ -277,6 +437,8 @@
      * point to the end of the decrypted/depadded data.  The initial
      * limit and new limit may be different, given we may
      * have stripped off some padding bytes.
+     *
+     *  @see decrypt(byte[], int, int)
      */
     int decrypt(ByteBuffer bb) throws BadPaddingException {
 
@@ -292,7 +454,6 @@
              * Decrypt "in-place".
              */
             int pos = bb.position();
-
             ByteBuffer dup = bb.duplicate();
             int newLen = cipher.update(dup, bb);
             if (newLen != len) {
@@ -320,6 +481,33 @@
             if (blockSize != 0) {
                 bb.position(pos);
                 newLen = removePadding(bb, blockSize, protocolVersion);
+
+                if (protocolVersion.v >= ProtocolVersion.TLS11.v) {
+                    if (newLen < blockSize) {
+                        throw new BadPaddingException("invalid explicit IV");
+                    }
+
+                    // discards the first cipher block, the IV component.
+                    byte[] buf = null;
+                    int limit = bb.limit();
+                    if (bb.hasArray()) {
+                        buf = bb.array();
+                        System.arraycopy(buf, pos + blockSize,
+                                         buf, pos, limit - pos - blockSize);
+                        bb.limit(limit - blockSize);
+                    } else {
+                        buf = new byte[limit - pos - blockSize];
+                        bb.position(pos + blockSize);
+                        bb.get(buf);
+                        bb.position(pos);
+                        bb.put(buf);
+                        bb.limit(limit - blockSize);
+                    }
+
+                    // reset the position to the end of the decrypted data
+                    limit = bb.limit();
+                    bb.position(limit);
+                }
             }
             return newLen;
         } catch (ShortBufferException e) {