JDK-6501179 : Sun's PKCS11-Solaris provider does not 'stream' properly
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto:pkcs11
  • Affected Version: 5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_10
  • CPU: x86
  • Submitted: 2006-12-06
  • Updated: 2011-02-16
  • Resolved: 2008-03-26
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_01-b08)
Java HotSpot(TM) Client VM (build 1.5.0_01-b08, mixed mode)
java version "1.5.0_10"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_10-b03)
Java HotSpot(TM) Client VM (build 1.5.0_10-b03, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
SunOS hostname 5.1 Generic i86pc i386 i386pc

A DESCRIPTION OF THE PROBLEM :
When using AES/CBC/NoPadding from the SunPKCS11-Solaris provider, streaming/buffering does not work correctly.

If two blocks are presented to the Cipher, with one oversize and one under, adding to the correct number of bytes for two blocks, the result is correct.

However, if three blocks are presented to the Cipher, with two oversized and one under, adding to the correct number of bytes for three blocks, the result is incorrect, and inconsistent with the SunJCE provider.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please consider source code below

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
SunPKCS11-Solaris provider should match the output of the SunJCE provider.
ACTUAL -
(safe is a flag to implement application level buffering instead of relying on the Cipher implementation)

SunJCE ( ** safe == false ** )
    [010] 93 ef 89 79 94 80 a2 9e 58 13 88 03 d5 5c 4d 8f
SunJCE ( ** safe == true ** )
    [010] 93 ef 89 79 94 80 a2 9e 58 13 88 03 d5 5c 4d 8f
SunPKCS11-Solaris ( ** safe == false ** )
    [010] 67 07 a5 6b a5 a0 17 3c 97 00 ef da d7 6a a7 c3
SunPKCS11-Solaris ( ** safe == true ** )
    [010] 93 ef 89 79 94 80 a2 9e 58 13 88 03 d5 5c 4d 8f

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Test attempting to demonstrate SunPKCS11-Solaris provider is not 'streaming'
 * properly. When two oversize blocks are sent to the Cipher object - the 3rd
 * encryption is incorrect. (One oversize block followed by correct sized block
 * works as expected.)
 *
 * @author rwilliam
 */
public class MismatchTest {
  /**
   * Run the mismatch test.
   *
   * @param args
   */
  public static void main(String[] args) {
    System.out.println("---------------------------------------");
    System.out.println(" Attempting to demonstrate strange");
    System.out.println(" behaviour inside SunPKCS11-Solaris");
    System.out.println(" provider. AES/CBC/NoPadding is used");
    System.out.println(" block size is 0x10.");
    new MismatchTest();
  }

  /**
   * Stream used to cache the block overflows until next time.
   */
  protected ByteArrayOutputStream dataCache = null;

  /**
   * Simple IV data.
   */
  byte[] ivData = new byte[16];

  /**
   * Simple key.
   */
  byte[] keyData = new byte[16];

  /**
   * Providers to test.
   */
  String[] providerNames = new String[] { "SunJCE", "SunPKCS11-Solaris" };

  /**
   * summary will hold strings to print out at the end of the test. Should
   * contain the provider name and the resulted cipher text.
   */
  ArrayList<String> summary = new ArrayList<String>();

  /**
   * Three chunks to be encrypted. 19+13 = 32 = 2 full blocks followed by
   * 18+20+10 = 48 = 3 full blocks.
   */
  byte[][] updateData = new byte[][] { makeByte(19), makeByte(13),
      makeByte(18), makeByte(20), makeByte(10) };

  /**
   * For each provider, run the test in not-safe and safe mode. Then print a
   * summary.
   */
  MismatchTest() {
    for (String s : providerNames) {
      for (int i = 0; i < 2; ++i) {
        testProvider(s, i == 1);
      }
    }
    System.out.println("--");
    for (String s : summary) {
      System.out.println(s);
    }
  }

  /**
   * Make a byte array of the given length with each byte set to the length
   *
   * @param length
   * @return A byte array of the given length with each byte set to the length
   */
  byte[] makeByte(int length) {
    byte[] toReturn = new byte[length];
    for (int i = 0; i < length; ++i) {
      toReturn[i] = (byte) length;
    }
    return toReturn;
  }

  /**
   * String representation of a byte array, preceded with length in []
   * brackets. All values are in hex.
   *
   * @param update
   * @return
   */
  String makePretty(byte[] update) {
    String toReturn = "[";

    if (update.length < 256) {
      toReturn += '0';
    }
    if (update.length < 16) {
      toReturn += '0';
    }
    toReturn += Integer.toHexString(update.length);

    toReturn += "] ";

    for (int b : update) {
      b &= 0xff;
      if (b < 0x10) {
        toReturn += "0";
      }
      toReturn += Integer.toHexString(b);
      toReturn += " ";
    }
    return toReturn;
  }

  /**
   * Test the given provider.
   *
   * @param provider
   *          The text name of the provider to test.
   * @param safe
   *          If buffering should be implemented locally instead of inside the
   *          Cipher instance.
   */
  void testProvider(String provider, boolean safe) {
    try {
      if (safe) {
        dataCache = new ByteArrayOutputStream();
      }

      System.out.println("-- Attempting " + provider + " ( ** safe == " + safe
          + " ** ) --");

      Cipher c = Cipher.getInstance("AES/CBC/NoPadding", provider);

      IvParameterSpec iv = new IvParameterSpec(ivData);
      SecretKeySpec key = new SecretKeySpec(keyData, "AES");

      c.init(Cipher.ENCRYPT_MODE, key, iv);

      System.out.println("Init with key: " + makePretty(keyData));
      System.out.println("           iv: " + makePretty(ivData));

      byte[] result = null;

      for (byte[] update : updateData) {
        System.out.println("  Update with: " + makePretty(update));
        result = update(c, update, safe);
        System.out.println("       Result: " + makePretty(result));
      }

      summary.add(provider + " ( ** safe == " + safe + " ** )\n    "
          + makePretty(result));

    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }

  /**
   * Update the given cipher with the given data. If safe is set, only complete
   * blocks will be sent to the Cipher.
   *
   * @param c
   *          The Cipher to use
   * @param data
   *          The data to send to the Cipher
   * @param safe
   *          If true, local buffering is used - and only complete blocks are
   *          sent to the Cipher
   * @return Cipher text
   * @throws IOException
   *           If things get bad
   */
  byte[] update(Cipher c, byte[] data, boolean safe) throws IOException {
    int blockSize = c.getBlockSize();
    if (!safe) {
      return c.update(data);
    } else {
      ByteArrayOutputStream toReturn = new ByteArrayOutputStream();

      for (int i = 0; i < data.length; ++i) {
        this.dataCache.write(data[i]);
        if (this.dataCache.size() != 0
            && this.dataCache.size() % blockSize == 0) {
          byte[] toUpdate = this.dataCache.toByteArray();
          System.out.println(" (SafeUpdate): " + makePretty(toUpdate));
          byte[] newData = c.update(toUpdate);
          if (newData != null) {
            toReturn.write(newData);
          }
          this.dataCache.reset();
        }
      }

      return toReturn.toByteArray();
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Applications can work around the problem by not relying on the streaming inside the Cipher implementation, and only ever present the Cipher with complete blocks.

Comments
EVALUATION True, it's due to a bug in SunPKCS11 provider's native code which uses the same buffer for input and output. Although the PKCS#11 spec documents this being valid, however, for certain input combinations, the output overwrites the not-yet-processed input and leads to incorrect result. This has be addressed as a by-product while fixing 4898484 "Cipher should optimize cases where only one buffer is a DirectBuffer". Thus, will be closing this as a duplicate of 4898484.
26-03-2008