4898461: Support for ECB and CBC/PKCS5Padding
authorvaleriep
Thu, 20 Mar 2008 16:02:23 -0700
changeset 289 b1c8ad73578f
parent 103 2ccefd4e038f
child 290 519d4185fbe2
4898461: Support for ECB and CBC/PKCS5Padding Summary: Add support for ECB mode and PKCS5Padding Reviewed-by: andreas
jdk/src/share/classes/sun/security/pkcs11/P11Cipher.java
jdk/src/share/classes/sun/security/pkcs11/SunPKCS11.java
jdk/test/sun/security/pkcs11/Cipher/TestSymmCiphers.java
--- a/jdk/src/share/classes/sun/security/pkcs11/P11Cipher.java	Mon Mar 17 12:27:58 2008 -0700
+++ b/jdk/src/share/classes/sun/security/pkcs11/P11Cipher.java	Thu Mar 20 16:02:23 2008 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2003-2008 Sun Microsystems, Inc.  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
@@ -22,10 +22,10 @@
  * CA 95054 USA or visit www.sun.com if you need additional information or
  * have any questions.
  */
-
 package sun.security.pkcs11;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import java.security.*;
 import java.security.spec.*;
@@ -34,7 +34,6 @@
 import javax.crypto.spec.*;
 
 import sun.nio.ch.DirectBuffer;
-
 import sun.security.pkcs11.wrapper.*;
 import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
 
@@ -43,8 +42,8 @@
  * DES, DESede, AES, ARCFOUR, and Blowfish.
  *
  * This class is designed to support ECB and CBC with NoPadding and
- * PKCS5Padding for both. However, currently only CBC/NoPadding (and
- * ECB/NoPadding for stream ciphers) is functional.
+ * PKCS5Padding for both. It will use its own padding impl if the
+ * native mechanism does not support padding.
  *
  * Note that PKCS#11 current only supports ECB and CBC. There are no
  * provisions for other modes such as CFB, OFB, PCBC, or CTR mode.
@@ -62,10 +61,56 @@
     private final static int MODE_CBC = 4;
 
     // padding constant for NoPadding
-    private final static int PAD_NONE  = 5;
+    private final static int PAD_NONE = 5;
     // padding constant for PKCS5Padding
     private final static int PAD_PKCS5 = 6;
 
+    private static interface Padding {
+        // ENC: format the specified buffer with padding bytes and return the
+        // actual padding length
+        int setPaddingBytes(byte[] paddingBuffer, int padLen);
+
+        // DEC: return the length of trailing padding bytes given the specified
+        // padded data
+        int unpad(byte[] paddedData, int ofs, int len)
+                throws BadPaddingException;
+    }
+
+    private static class PKCS5Padding implements Padding {
+
+        private final int blockSize;
+
+        PKCS5Padding(int blockSize)
+                throws NoSuchPaddingException {
+            if (blockSize == 0) {
+                throw new NoSuchPaddingException
+                        ("PKCS#5 padding not supported with stream ciphers");
+            }
+            this.blockSize = blockSize;
+        }
+
+        public int setPaddingBytes(byte[] paddingBuffer, int padLen) {
+            Arrays.fill(paddingBuffer, 0, padLen, (byte) (padLen & 0x007f));
+            return padLen;
+        }
+
+        public int unpad(byte[] paddedData, int ofs, int len)
+                throws BadPaddingException {
+            byte padValue = paddedData[ofs + len - 1];
+            if (padValue < 1 || padValue > blockSize) {
+                throw new BadPaddingException("Invalid pad value!");
+            }
+            // sanity check padding bytes
+            int padStartIndex = ofs + len - padValue;
+            for (int i = padStartIndex; i < len; i++) {
+                if (paddedData[i] != padValue) {
+                    throw new BadPaddingException("Invalid pad bytes!");
+                }
+            }
+            return padValue;
+        }
+    }
+
     // token instance
     private final Token token;
 
@@ -99,65 +144,93 @@
     // padding type, on of PAD_* above (PAD_NONE for stream ciphers)
     private int paddingType;
 
+    // when the padding is requested but unsupported by the native mechanism,
+    // we use the following to do padding and necessary data buffering.
+    // padding object which generate padding and unpad the decrypted data
+    private Padding paddingObj;
+    // buffer for holding back the block which contains padding bytes
+    private byte[] padBuffer;
+    private int padBufferLen;
+
     // original IV, if in MODE_CBC
     private byte[] iv;
 
-    // total number of bytes processed
-    private int bytesProcessed;
+    // number of bytes buffered internally by the native mechanism and padBuffer
+    // if we do the padding
+    private int bytesBuffered;
 
     P11Cipher(Token token, String algorithm, long mechanism)
-            throws PKCS11Exception {
+            throws PKCS11Exception, NoSuchAlgorithmException {
         super();
         this.token = token;
         this.algorithm = algorithm;
         this.mechanism = mechanism;
-        keyAlgorithm = algorithm.split("/")[0];
+
+        String algoParts[] = algorithm.split("/");
+        keyAlgorithm = algoParts[0];
+
         if (keyAlgorithm.equals("AES")) {
             blockSize = 16;
-            blockMode = MODE_CBC;
-            // XXX change default to PKCS5Padding
-            paddingType = PAD_NONE;
-        } else if (keyAlgorithm.equals("RC4") || keyAlgorithm.equals("ARCFOUR")) {
+        } else if (keyAlgorithm.equals("RC4") ||
+                keyAlgorithm.equals("ARCFOUR")) {
             blockSize = 0;
-            blockMode = MODE_ECB;
-            paddingType = PAD_NONE;
         } else { // DES, DESede, Blowfish
             blockSize = 8;
-            blockMode = MODE_CBC;
-            // XXX change default to PKCS5Padding
-            paddingType = PAD_NONE;
+        }
+        this.blockMode =
+                (algoParts.length > 1 ? parseMode(algoParts[1]) : MODE_ECB);
+
+        String defPadding = (blockSize == 0 ? "NoPadding" : "PKCS5Padding");
+        String paddingStr =
+                (algoParts.length > 2 ? algoParts[2] : defPadding);
+        try {
+            engineSetPadding(paddingStr);
+        } catch (NoSuchPaddingException nspe) {
+            // should not happen
+            throw new ProviderException(nspe);
         }
         session = token.getOpSession();
     }
 
     protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
+        // Disallow change of mode for now since currently it's explicitly
+        // defined in transformation strings
+        throw new NoSuchAlgorithmException("Unsupported mode " + mode);
+    }
+
+    private int parseMode(String mode) throws NoSuchAlgorithmException {
         mode = mode.toUpperCase();
+        int result;
         if (mode.equals("ECB")) {
-            this.blockMode = MODE_ECB;
+            result = MODE_ECB;
         } else if (mode.equals("CBC")) {
             if (blockSize == 0) {
                 throw new NoSuchAlgorithmException
                         ("CBC mode not supported with stream ciphers");
             }
-            this.blockMode = MODE_CBC;
+            result = MODE_CBC;
         } else {
             throw new NoSuchAlgorithmException("Unsupported mode " + mode);
         }
+        return result;
     }
 
     // see JCE spec
     protected void engineSetPadding(String padding)
             throws NoSuchPaddingException {
-        if (padding.equalsIgnoreCase("NoPadding")) {
+        paddingObj = null;
+        padBuffer = null;
+        padding = padding.toUpperCase();
+        if (padding.equals("NOPADDING")) {
             paddingType = PAD_NONE;
-        } else if (padding.equalsIgnoreCase("PKCS5Padding")) {
-            if (blockSize == 0) {
-                throw new NoSuchPaddingException
-                        ("PKCS#5 padding not supported with stream ciphers");
+        } else if (padding.equals("PKCS5PADDING")) {
+            paddingType = PAD_PKCS5;
+            if (mechanism != CKM_DES_CBC_PAD && mechanism != CKM_DES3_CBC_PAD &&
+                    mechanism != CKM_AES_CBC_PAD) {
+                // no native padding support; use our own padding impl
+                paddingObj = new PKCS5Padding(blockSize);
+                padBuffer = new byte[blockSize];
             }
-            paddingType = PAD_PKCS5;
-            // XXX PKCS#5 not yet implemented
-            throw new NoSuchPaddingException("pkcs5");
         } else {
             throw new NoSuchPaddingException("Unsupported padding " + padding);
         }
@@ -175,7 +248,7 @@
 
     // see JCE spec
     protected byte[] engineGetIV() {
-        return (iv == null) ? null : (byte[])iv.clone();
+        return (iv == null) ? null : (byte[]) iv.clone();
     }
 
     // see JCE spec
@@ -185,8 +258,9 @@
         }
         IvParameterSpec ivSpec = new IvParameterSpec(iv);
         try {
-            AlgorithmParameters params = AlgorithmParameters.getInstance
-                (keyAlgorithm, P11Util.getSunJceProvider());
+            AlgorithmParameters params =
+                    AlgorithmParameters.getInstance(keyAlgorithm,
+                    P11Util.getSunJceProvider());
             params.init(ivSpec);
             return params;
         } catch (GeneralSecurityException e) {
@@ -210,38 +284,38 @@
     protected void engineInit(int opmode, Key key,
             AlgorithmParameterSpec params, SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
-        byte[] iv;
+        byte[] ivValue;
         if (params != null) {
             if (params instanceof IvParameterSpec == false) {
                 throw new InvalidAlgorithmParameterException
                         ("Only IvParameterSpec supported");
             }
-            IvParameterSpec ivSpec = (IvParameterSpec)params;
-            iv = ivSpec.getIV();
+            IvParameterSpec ivSpec = (IvParameterSpec) params;
+            ivValue = ivSpec.getIV();
         } else {
-            iv = null;
+            ivValue = null;
         }
-        implInit(opmode, key, iv, random);
+        implInit(opmode, key, ivValue, random);
     }
 
     // see JCE spec
     protected void engineInit(int opmode, Key key, AlgorithmParameters params,
             SecureRandom random)
             throws InvalidKeyException, InvalidAlgorithmParameterException {
-        byte[] iv;
+        byte[] ivValue;
         if (params != null) {
             try {
                 IvParameterSpec ivSpec = (IvParameterSpec)
                         params.getParameterSpec(IvParameterSpec.class);
-                iv = ivSpec.getIV();
+                ivValue = ivSpec.getIV();
             } catch (InvalidParameterSpecException e) {
                 throw new InvalidAlgorithmParameterException
                         ("Could not decode IV", e);
             }
         } else {
-            iv = null;
+            ivValue = null;
         }
-        implInit(opmode, key, iv, random);
+        implInit(opmode, key, ivValue, random);
     }
 
     // actual init() implementation
@@ -250,31 +324,31 @@
             throws InvalidKeyException, InvalidAlgorithmParameterException {
         cancelOperation();
         switch (opmode) {
-        case Cipher.ENCRYPT_MODE:
-            encrypt = true;
-            break;
-        case Cipher.DECRYPT_MODE:
-            encrypt = false;
-            break;
-        default:
-            throw new InvalidAlgorithmParameterException
-                ("Unsupported mode: " + opmode);
+            case Cipher.ENCRYPT_MODE:
+                encrypt = true;
+                break;
+            case Cipher.DECRYPT_MODE:
+                encrypt = false;
+                break;
+            default:
+                throw new InvalidAlgorithmParameterException
+                        ("Unsupported mode: " + opmode);
         }
         if (blockMode == MODE_ECB) { // ECB or stream cipher
             if (iv != null) {
                 if (blockSize == 0) {
                     throw new InvalidAlgorithmParameterException
-                        ("IV not used with stream ciphers");
+                            ("IV not used with stream ciphers");
                 } else {
                     throw new InvalidAlgorithmParameterException
-                        ("IV not used in ECB mode");
+                            ("IV not used in ECB mode");
                 }
             }
         } else { // MODE_CBC
             if (iv == null) {
                 if (encrypt == false) {
                     throw new InvalidAlgorithmParameterException
-                        ("IV must be specified for decryption in CBC mode");
+                            ("IV must be specified for decryption in CBC mode");
                 }
                 // generate random IV
                 if (random == null) {
@@ -285,7 +359,7 @@
             } else {
                 if (iv.length != blockSize) {
                     throw new InvalidAlgorithmParameterException
-                        ("IV length must match block size");
+                            ("IV length must match block size");
                 }
             }
         }
@@ -331,63 +405,43 @@
             session = token.getOpSession();
         }
         if (encrypt) {
-            token.p11.C_EncryptInit
-                (session.id(), new CK_MECHANISM(mechanism, iv), p11Key.keyID);
+            token.p11.C_EncryptInit(session.id(),
+                    new CK_MECHANISM(mechanism, iv), p11Key.keyID);
         } else {
-            token.p11.C_DecryptInit
-                (session.id(), new CK_MECHANISM(mechanism, iv), p11Key.keyID);
+            token.p11.C_DecryptInit(session.id(),
+                    new CK_MECHANISM(mechanism, iv), p11Key.keyID);
         }
-        bytesProcessed = 0;
+        bytesBuffered = 0;
+        padBufferLen = 0;
         initialized = true;
     }
 
-    // XXX the calculations below assume the PKCS#11 implementation is smart.
-    // conceivably, not all implementations are and we may need to estimate
-    // more conservatively
-
-    private int bytesBuffered(int totalLen) {
-        if (paddingType == PAD_NONE) {
-            // with NoPadding, buffer only the current unfinished block
-            return totalLen & (blockSize - 1);
-        } else { // PKCS5
-            // with PKCS5Padding in decrypt mode, the buffer must never
-            // be empty. Buffer a full block instead of nothing.
-            int buffered = totalLen & (blockSize - 1);
-            if ((buffered == 0) && (encrypt == false)) {
-                buffered = blockSize;
-            }
-            return buffered;
-        }
-    }
-
     // if update(inLen) is called, how big does the output buffer have to be?
     private int updateLength(int inLen) {
         if (inLen <= 0) {
             return 0;
         }
-        if (blockSize == 0) {
-            return inLen;
-        } else {
-            // bytes that need to be buffered now
-            int buffered = bytesBuffered(bytesProcessed);
-            // bytes that need to be buffered after this update
-            int newBuffered = bytesBuffered(bytesProcessed + inLen);
-            return inLen + buffered - newBuffered;
+
+        int result = inLen + bytesBuffered;
+        if (blockSize != 0) {
+            // minus the number of bytes in the last incomplete block.
+            result -= (result & (blockSize - 1));
         }
+        return result;
     }
 
     // if doFinal(inLen) is called, how big does the output buffer have to be?
     private int doFinalLength(int inLen) {
-        if (paddingType == PAD_NONE) {
-            return updateLength(inLen);
-        }
         if (inLen < 0) {
             return 0;
         }
-        int buffered = bytesBuffered(bytesProcessed);
-        int newProcessed = bytesProcessed + inLen;
-        int paddedProcessed = (newProcessed + blockSize) & ~(blockSize - 1);
-        return paddedProcessed - bytesProcessed + buffered;
+
+        int result = inLen + bytesBuffered;
+        if (blockSize != 0 && encrypt && paddingType != PAD_NONE) {
+            // add the number of bytes to make the last block complete.
+            result += (blockSize - (result & (blockSize - 1)));
+        }
+        return result;
     }
 
     // see JCE spec
@@ -397,6 +451,7 @@
             int n = engineUpdate(in, inOfs, inLen, out, 0);
             return P11Util.convert(out, 0, n);
         } catch (ShortBufferException e) {
+            // convert since the output length is calculated by updateLength()
             throw new ProviderException(e);
         }
     }
@@ -409,6 +464,7 @@
     }
 
     // see JCE spec
+    @Override
     protected int engineUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer)
             throws ShortBufferException {
         return implUpdate(inBuffer, outBuffer);
@@ -422,14 +478,15 @@
             int n = engineDoFinal(in, inOfs, inLen, out, 0);
             return P11Util.convert(out, 0, n);
         } catch (ShortBufferException e) {
+            // convert since the output length is calculated by doFinalLength()
             throw new ProviderException(e);
         }
     }
 
     // see JCE spec
     protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out,
-            int outOfs) throws ShortBufferException, IllegalBlockSizeException {
-            // BadPaddingException {
+            int outOfs) throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
         int n = 0;
         if ((inLen != 0) && (in != null)) {
             n = engineUpdate(in, inOfs, inLen, out, outOfs);
@@ -440,8 +497,10 @@
     }
 
     // see JCE spec
+    @Override
     protected int engineDoFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
-    throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
         int n = engineUpdate(inBuffer, outBuffer);
         n += implDoFinal(outBuffer);
         return n;
@@ -454,18 +513,55 @@
         }
         try {
             ensureInitialized();
-            int k;
+            int k = 0;
             if (encrypt) {
-                k = token.p11.C_EncryptUpdate
-                (session.id(), 0, in, inOfs, inLen, 0, out, outOfs, outLen);
+                k = token.p11.C_EncryptUpdate(session.id(), 0, in, inOfs, inLen,
+                        0, out, outOfs, outLen);
             } else {
-                k = token.p11.C_DecryptUpdate
-                (session.id(), 0, in, inOfs, inLen, 0, out, outOfs, outLen);
+                int newPadBufferLen = 0;
+                if (paddingObj != null) {
+                    if (padBufferLen != 0) {
+                        // NSS throws up when called with data not in multiple
+                        // of blocks. Try to work around this by holding the
+                        // extra data in padBuffer.
+                        if (padBufferLen != padBuffer.length) {
+                            int bufCapacity = padBuffer.length - padBufferLen;
+                            if (inLen > bufCapacity) {
+                                bufferInputBytes(in, inOfs, bufCapacity);
+                                inOfs += bufCapacity;
+                                inLen -= bufCapacity;
+                            } else {
+                                bufferInputBytes(in, inOfs, inLen);
+                                return 0;
+                            }
+                        }
+                        k = token.p11.C_DecryptUpdate(session.id(),
+                                0, padBuffer, 0, padBufferLen,
+                                0, out, outOfs, outLen);
+                        padBufferLen = 0;
+                    }
+                    newPadBufferLen = inLen & (blockSize - 1);
+                    if (newPadBufferLen == 0) {
+                        newPadBufferLen = padBuffer.length;
+                    }
+                    inLen -= newPadBufferLen;
+                }
+                if (inLen > 0) {
+                    k += token.p11.C_DecryptUpdate(session.id(), 0, in, inOfs,
+                            inLen, 0, out, (outOfs + k), (outLen - k));
+                }
+                // update 'padBuffer' if using our own padding impl.
+                if (paddingObj != null) {
+                    bufferInputBytes(in, inOfs + inLen, newPadBufferLen);
+                }
             }
-            bytesProcessed += inLen;
+            bytesBuffered += (inLen - k);
             return k;
         } catch (PKCS11Exception e) {
-            // XXX throw correct exception
+            if (e.getErrorCode() == CKR_BUFFER_TOO_SMALL) {
+                throw (ShortBufferException)
+                        (new ShortBufferException().initCause(e));
+            }
             throw new ProviderException("update() failed", e);
         }
     }
@@ -481,101 +577,167 @@
         if (outLen < updateLength(inLen)) {
             throw new ShortBufferException();
         }
-        boolean inPosChanged = false;
+        int origPos = inBuffer.position();
         try {
             ensureInitialized();
 
             long inAddr = 0;
-            int inOfs = inBuffer.position();
+            int inOfs = 0;
             byte[] inArray = null;
+
             if (inBuffer instanceof DirectBuffer) {
-                inAddr = ((DirectBuffer)inBuffer).address();
-            } else {
-                if (inBuffer.hasArray()) {
-                    inArray = inBuffer.array();
-                    inOfs += inBuffer.arrayOffset();
-                } else {
-                    inArray = new byte[inLen];
-                    inBuffer.get(inArray);
-                    inOfs = 0;
-                    inPosChanged = true;
-                }
+                inAddr = ((DirectBuffer) inBuffer).address();
+                inOfs = origPos;
+            } else if (inBuffer.hasArray()) {
+                inArray = inBuffer.array();
+                inOfs = (origPos + inBuffer.arrayOffset());
             }
 
             long outAddr = 0;
-            int outOfs = outBuffer.position();
+            int outOfs = 0;
             byte[] outArray = null;
             if (outBuffer instanceof DirectBuffer) {
-                outAddr = ((DirectBuffer)outBuffer).address();
+                outAddr = ((DirectBuffer) outBuffer).address();
+                outOfs = outBuffer.position();
             } else {
                 if (outBuffer.hasArray()) {
                     outArray = outBuffer.array();
-                    outOfs += outBuffer.arrayOffset();
+                    outOfs = (outBuffer.position() + outBuffer.arrayOffset());
                 } else {
                     outArray = new byte[outLen];
-                    outOfs = 0;
                 }
             }
 
-            int k;
+            int k = 0;
             if (encrypt) {
-                k = token.p11.C_EncryptUpdate
-                    (session.id(), inAddr, inArray, inOfs, inLen,
-                     outAddr, outArray, outOfs, outLen);
+                if (inAddr == 0 && inArray == null) {
+                    inArray = new byte[inLen];
+                    inBuffer.get(inArray);
+                } else {
+                    inBuffer.position(origPos + inLen);
+                }
+                k = token.p11.C_EncryptUpdate(session.id(),
+                        inAddr, inArray, inOfs, inLen,
+                        outAddr, outArray, outOfs, outLen);
             } else {
-                k = token.p11.C_DecryptUpdate
-                    (session.id(), inAddr, inArray, inOfs, inLen,
-                     outAddr, outArray, outOfs, outLen);
+                int newPadBufferLen = 0;
+                if (paddingObj != null) {
+                    if (padBufferLen != 0) {
+                        // NSS throws up when called with data not in multiple
+                        // of blocks. Try to work around this by holding the
+                        // extra data in padBuffer.
+                        if (padBufferLen != padBuffer.length) {
+                            int bufCapacity = padBuffer.length - padBufferLen;
+                            if (inLen > bufCapacity) {
+                                bufferInputBytes(inBuffer, bufCapacity);
+                                inOfs += bufCapacity;
+                                inLen -= bufCapacity;
+                            } else {
+                                bufferInputBytes(inBuffer, inLen);
+                                return 0;
+                            }
+                        }
+                        k = token.p11.C_DecryptUpdate(session.id(), 0,
+                                padBuffer, 0, padBufferLen, outAddr, outArray,
+                                outOfs, outLen);
+                        padBufferLen = 0;
+                    }
+                    newPadBufferLen = inLen & (blockSize - 1);
+                    if (newPadBufferLen == 0) {
+                        newPadBufferLen = padBuffer.length;
+                    }
+                    inLen -= newPadBufferLen;
+                }
+                if (inLen > 0) {
+                    if (inAddr == 0 && inArray == null) {
+                        inArray = new byte[inLen];
+                        inBuffer.get(inArray);
+                    } else {
+                        inBuffer.position(inBuffer.position() + inLen);
+                    }
+                    k += token.p11.C_DecryptUpdate(session.id(), inAddr,
+                            inArray, inOfs, inLen, outAddr, outArray,
+                            (outOfs + k), (outLen - k));
+                }
+                // update 'padBuffer' if using our own padding impl.
+                if (paddingObj != null && newPadBufferLen != 0) {
+                    bufferInputBytes(inBuffer, newPadBufferLen);
+                }
             }
-            bytesProcessed += inLen;
-            if (!inPosChanged) {
-                inBuffer.position(inBuffer.position() + inLen);
-            }
+            bytesBuffered += (inLen - k);
             if (!(outBuffer instanceof DirectBuffer) &&
-                !outBuffer.hasArray()) {
+                    !outBuffer.hasArray()) {
                 outBuffer.put(outArray, outOfs, k);
             } else {
                 outBuffer.position(outBuffer.position() + k);
             }
             return k;
         } catch (PKCS11Exception e) {
-            // Un-read the bytes back to input buffer
-            if (inPosChanged) {
-                inBuffer.position(inBuffer.position() - inLen);
+            // Reset input buffer to its original position for
+            inBuffer.position(origPos);
+            if (e.getErrorCode() == CKR_BUFFER_TOO_SMALL) {
+                throw (ShortBufferException)
+                        (new ShortBufferException().initCause(e));
             }
-            // XXX throw correct exception
             throw new ProviderException("update() failed", e);
         }
     }
 
     private int implDoFinal(byte[] out, int outOfs, int outLen)
-            throws ShortBufferException, IllegalBlockSizeException {
-        if (outLen < doFinalLength(0)) {
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
+        int requiredOutLen = doFinalLength(0);
+        if (outLen < requiredOutLen) {
             throw new ShortBufferException();
         }
         try {
             ensureInitialized();
+            int k = 0;
             if (encrypt) {
-                return token.p11.C_EncryptFinal
-                                (session.id(), 0, out, outOfs, outLen);
+                if (paddingObj != null) {
+                    int actualPadLen = paddingObj.setPaddingBytes(padBuffer,
+                            requiredOutLen - bytesBuffered);
+                    k = token.p11.C_EncryptUpdate(session.id(),
+                            0, padBuffer, 0, actualPadLen,
+                            0, out, outOfs, outLen);
+                }
+                k += token.p11.C_EncryptFinal(session.id(),
+                        0, out, (outOfs + k), (outLen - k));
             } else {
-                return token.p11.C_DecryptFinal
-                                (session.id(), 0, out, outOfs, outLen);
+                if (paddingObj != null) {
+                    if (padBufferLen != 0) {
+                        k = token.p11.C_DecryptUpdate(session.id(), 0,
+                                padBuffer, 0, padBufferLen, 0, padBuffer, 0,
+                                padBuffer.length);
+                    }
+                    k += token.p11.C_DecryptFinal(session.id(), 0, padBuffer, k,
+                            padBuffer.length - k);
+                    int actualPadLen = paddingObj.unpad(padBuffer, 0, k);
+                    k -= actualPadLen;
+                    System.arraycopy(padBuffer, 0, out, outOfs, k);
+                } else {
+                    k = token.p11.C_DecryptFinal(session.id(), 0, out, outOfs,
+                            outLen);
+                }
             }
+            return k;
         } catch (PKCS11Exception e) {
             handleException(e);
             throw new ProviderException("doFinal() failed", e);
         } finally {
             initialized = false;
-            bytesProcessed = 0;
+            bytesBuffered = 0;
+            padBufferLen = 0;
             session = token.releaseSession(session);
         }
     }
 
     private int implDoFinal(ByteBuffer outBuffer)
-            throws ShortBufferException, IllegalBlockSizeException {
+            throws ShortBufferException, IllegalBlockSizeException,
+            BadPaddingException {
         int outLen = outBuffer.remaining();
-        if (outLen < doFinalLength(0)) {
+        int requiredOutLen = doFinalLength(0);
+        if (outLen < requiredOutLen) {
             throw new ShortBufferException();
         }
 
@@ -583,30 +745,54 @@
             ensureInitialized();
 
             long outAddr = 0;
-            int outOfs = outBuffer.position();
             byte[] outArray = null;
+            int outOfs = 0;
             if (outBuffer instanceof DirectBuffer) {
-                outAddr = ((DirectBuffer)outBuffer).address();
+                outAddr = ((DirectBuffer) outBuffer).address();
+                outOfs = outBuffer.position();
             } else {
                 if (outBuffer.hasArray()) {
                     outArray = outBuffer.array();
-                    outOfs += outBuffer.arrayOffset();
+                    outOfs = outBuffer.position() + outBuffer.arrayOffset();
                 } else {
                     outArray = new byte[outLen];
-                    outOfs = 0;
                 }
             }
 
-            int k;
+            int k = 0;
+
             if (encrypt) {
-                k = token.p11.C_EncryptFinal
-                    (session.id(), outAddr, outArray, outOfs, outLen);
+                if (paddingObj != null) {
+                    int actualPadLen = paddingObj.setPaddingBytes(padBuffer,
+                            requiredOutLen - bytesBuffered);
+                    k = token.p11.C_EncryptUpdate(session.id(),
+                            0, padBuffer, 0, actualPadLen,
+                            outAddr, outArray, outOfs, outLen);
+                }
+                k += token.p11.C_EncryptFinal(session.id(),
+                        outAddr, outArray, (outOfs + k), (outLen - k));
             } else {
-                k = token.p11.C_DecryptFinal
-                    (session.id(), outAddr, outArray, outOfs, outLen);
+                if (paddingObj != null) {
+                    if (padBufferLen != 0) {
+                        k = token.p11.C_DecryptUpdate(session.id(),
+                                0, padBuffer, 0, padBufferLen,
+                                0, padBuffer, 0, padBuffer.length);
+                        padBufferLen = 0;
+                    }
+                    k += token.p11.C_DecryptFinal(session.id(),
+                            0, padBuffer, k, padBuffer.length - k);
+                    int actualPadLen = paddingObj.unpad(padBuffer, 0, k);
+                    k -= actualPadLen;
+                    outArray = padBuffer;
+                    outOfs = 0;
+                } else {
+                    k = token.p11.C_DecryptFinal(session.id(),
+                            outAddr, outArray, outOfs, outLen);
+                }
             }
-            if (!(outBuffer instanceof DirectBuffer) &&
-                !outBuffer.hasArray()) {
+            if ((!encrypt && paddingObj != null) ||
+                    (!(outBuffer instanceof DirectBuffer) &&
+                    !outBuffer.hasArray())) {
                 outBuffer.put(outArray, outOfs, k);
             } else {
                 outBuffer.position(outBuffer.position() + k);
@@ -617,20 +803,21 @@
             throw new ProviderException("doFinal() failed", e);
         } finally {
             initialized = false;
-            bytesProcessed = 0;
+            bytesBuffered = 0;
             session = token.releaseSession(session);
         }
     }
 
     private void handleException(PKCS11Exception e)
-            throws IllegalBlockSizeException {
+            throws ShortBufferException, IllegalBlockSizeException {
         long errorCode = e.getErrorCode();
-        // XXX better check
-        if (errorCode == CKR_DATA_LEN_RANGE) {
-            throw (IllegalBlockSizeException)new
-                IllegalBlockSizeException(e.toString()).initCause(e);
+        if (errorCode == CKR_BUFFER_TOO_SMALL) {
+            throw (ShortBufferException)
+                    (new ShortBufferException().initCause(e));
+        } else if (errorCode == CKR_DATA_LEN_RANGE) {
+            throw (IllegalBlockSizeException)
+                    (new IllegalBlockSizeException(e.toString()).initCause(e));
         }
-
     }
 
     // see JCE spec
@@ -649,12 +836,14 @@
     }
 
     // see JCE spec
+    @Override
     protected int engineGetKeySize(Key key) throws InvalidKeyException {
         int n = P11SecretKeyFactory.convertKey
-                                (token, key, keyAlgorithm).keyLength();
+                (token, key, keyAlgorithm).keyLength();
         return n;
     }
 
+    @Override
     protected void finalize() throws Throwable {
         try {
             if ((session != null) && token.isValid()) {
@@ -666,4 +855,15 @@
         }
     }
 
+    private final void bufferInputBytes(byte[] in, int inOfs, int len) {
+        System.arraycopy(in, inOfs, padBuffer, padBufferLen, len);
+        padBufferLen += len;
+        bytesBuffered += len;
+    }
+
+    private final void bufferInputBytes(ByteBuffer inBuffer, int len) {
+        inBuffer.get(padBuffer, padBufferLen, len);
+        padBufferLen += len;
+        bytesBuffered += len;
+    }
 }
--- a/jdk/src/share/classes/sun/security/pkcs11/SunPKCS11.java	Mon Mar 17 12:27:58 2008 -0700
+++ b/jdk/src/share/classes/sun/security/pkcs11/SunPKCS11.java	Thu Mar 20 16:02:23 2008 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2003-2008 Sun Microsystems, Inc.  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
@@ -601,14 +601,26 @@
         // XXX attributes for Ciphers (supported modes, padding)
         d(CIP, "ARCFOUR",                       P11Cipher,      s("RC4"),
                 m(CKM_RC4));
-        // XXX only CBC/NoPadding for block ciphers
         d(CIP, "DES/CBC/NoPadding",             P11Cipher,
                 m(CKM_DES_CBC));
+        d(CIP, "DES/CBC/PKCS5Padding",          P11Cipher,
+                m(CKM_DES_CBC_PAD, CKM_DES_CBC));
+        d(CIP, "DES/ECB",                       P11Cipher,      s("DES"),
+                m(CKM_DES_ECB));
+
         d(CIP, "DESede/CBC/NoPadding",          P11Cipher,
                 m(CKM_DES3_CBC));
+        d(CIP, "DESede/CBC/PKCS5Padding",       P11Cipher,
+                m(CKM_DES3_CBC_PAD, CKM_DES3_CBC));
+        d(CIP, "DESede/ECB",                    P11Cipher,      s("DESede"),
+                m(CKM_DES3_ECB));
         d(CIP, "AES/CBC/NoPadding",             P11Cipher,
                 m(CKM_AES_CBC));
-        d(CIP, "Blowfish/CBC/NoPadding",        P11Cipher,
+        d(CIP, "AES/CBC/PKCS5Padding",          P11Cipher,
+                m(CKM_AES_CBC_PAD, CKM_AES_CBC));
+        d(CIP, "AES/ECB",                       P11Cipher,      s("AES"),
+                m(CKM_AES_ECB));
+        d(CIP, "Blowfish/CBC",                  P11Cipher,
                 m(CKM_BLOWFISH_CBC));
 
         // XXX RSA_X_509, RSA_OAEP not yet supported
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/pkcs11/Cipher/TestSymmCiphers.java	Thu Mar 20 16:02:23 2008 -0700
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc.  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 modi
+fy it
+ * under the terms of the GNU General Public License version 2 onl
+y, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, bu
+t WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABIL
+ITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public L
+icense
+ * version 2 for more details (a copy is included in the LICENSE f
+ile that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public Licen
+se version
+ * 2 along with this work; if not, write to the Free Software Foun
+dation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, San
+ta Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional inform
+ation or
+ * have any questions.
+ */
+
+/**
+ * @test %I% %E%
+ * @bug 4898461
+ * @summary basic test for symmetric ciphers with padding
+ * @author Valerie Peng
+ * @library ..
+ */
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+
+import java.security.*;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+
+public class TestSymmCiphers extends PKCS11Test {
+
+    private static class CI { // class for holding Cipher Information
+
+        String transformation;
+        String keyAlgo;
+        int dataSize;
+
+        CI(String transformation, String keyAlgo, int dataSize) {
+            this.transformation = transformation;
+            this.keyAlgo = keyAlgo;
+            this.dataSize = dataSize;
+        }
+    }
+    private static final CI[] TEST_LIST = {
+        new CI("ARCFOUR", "ARCFOUR", 400),
+        new CI("RC4", "RC4", 401),
+        new CI("DES/CBC/NoPadding", "DES", 400),
+        new CI("DESede/CBC/NoPadding", "DESede", 160),
+        new CI("AES/CBC/NoPadding", "AES", 4800),
+        new CI("Blowfish/CBC/NoPadding", "Blowfish", 24),
+        new CI("DES/cbc/PKCS5Padding", "DES", 6401),
+        new CI("DESede/CBC/PKCS5Padding", "DESede", 402),
+        new CI("AES/CBC/PKCS5Padding", "AES", 30),
+        new CI("Blowfish/CBC/PKCS5Padding", "Blowfish", 19),
+        new CI("DES/ECB/NoPadding", "DES", 400),
+        new CI("DESede/ECB/NoPadding", "DESede", 160),
+        new CI("AES/ECB/NoPadding", "AES", 4800),
+        new CI("DES/ECB/PKCS5Padding", "DES", 32),
+        new CI("DES/ECB/PKCS5Padding", "DES", 6400),
+        new CI("DESede/ECB/PKCS5Padding", "DESede", 400),
+        new CI("AES/ECB/PKCS5Padding", "AES", 64),
+        new CI("DES", "DES", 6400),
+        new CI("DESede", "DESede", 408),
+        new CI("AES", "AES", 128)
+    };
+    private static StringBuffer debugBuf = new StringBuffer();
+
+    public void main(Provider p) throws Exception {
+        // NSS reports CKR_DEVICE_ERROR when the data passed to
+        // its EncryptUpdate/DecryptUpdate is not multiple of blocks
+        int firstBlkSize = 16;
+        boolean status = true;
+        Random random = new Random();
+        try {
+            for (int i = 0; i < TEST_LIST.length; i++) {
+                CI currTest = TEST_LIST[i];
+                System.out.println("===" + currTest.transformation + "===");
+                try {
+                    KeyGenerator kg =
+                            KeyGenerator.getInstance(currTest.keyAlgo, p);
+                    SecretKey key = kg.generateKey();
+                    Cipher c1 = Cipher.getInstance(currTest.transformation, p);
+                    Cipher c2 = Cipher.getInstance(currTest.transformation,
+                            "SunJCE");
+
+                    byte[] plainTxt = new byte[currTest.dataSize];
+                    random.nextBytes(plainTxt);
+                    System.out.println("Testing inLen = " + plainTxt.length);
+
+                    c2.init(Cipher.ENCRYPT_MODE, key);
+                    AlgorithmParameters params = c2.getParameters();
+                    byte[] answer = c2.doFinal(plainTxt);
+                    System.out.println("Encryption tests: START");
+                    test(c1, Cipher.ENCRYPT_MODE, key, params, firstBlkSize,
+                            plainTxt, answer);
+                    System.out.println("Encryption tests: DONE");
+                    c2.init(Cipher.DECRYPT_MODE, key, params);
+                    byte[] answer2 = c2.doFinal(answer);
+                    System.out.println("Decryption tests: START");
+                    test(c1, Cipher.DECRYPT_MODE, key, params, firstBlkSize,
+                            answer, answer2);
+                    System.out.println("Decryption tests: DONE");
+                } catch (NoSuchAlgorithmException nsae) {
+                    System.out.println("Skipping unsupported algorithm: " +
+                            nsae);
+                }
+            }
+        } catch (Exception ex) {
+            // print out debug info when exception is encountered
+            if (debugBuf != null) {
+                System.out.println(debugBuf.toString());
+                debugBuf = new StringBuffer();
+            }
+            throw ex;
+        }
+    }
+
+    private static void test(Cipher cipher, int mode, SecretKey key,
+            AlgorithmParameters params, int firstBlkSize,
+            byte[] in, byte[] answer) throws Exception {
+        // test setup
+        long startTime, endTime;
+        cipher.init(mode, key, params);
+        int outLen = cipher.getOutputSize(in.length);
+        //debugOut("Estimated output size = " + outLen + "\n");
+
+        // test data preparation
+        ByteBuffer inBuf = ByteBuffer.allocate(in.length);
+        inBuf.put(in);
+        inBuf.position(0);
+        ByteBuffer inDirectBuf = ByteBuffer.allocateDirect(in.length);
+        inDirectBuf.put(in);
+        inDirectBuf.position(0);
+        ByteBuffer outBuf = ByteBuffer.allocate(outLen);
+        ByteBuffer outDirectBuf = ByteBuffer.allocateDirect(outLen);
+
+        // test#1: byte[] in + byte[] out
+        //debugOut("Test#1:\n");
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        startTime = System.nanoTime();
+        byte[] temp = cipher.update(in, 0, firstBlkSize);
+        if (temp != null && temp.length > 0) {
+            baos.write(temp, 0, temp.length);
+        }
+        temp = cipher.doFinal(in, firstBlkSize, in.length - firstBlkSize);
+        if (temp != null && temp.length > 0) {
+            baos.write(temp, 0, temp.length);
+        }
+        byte[] testOut1 = baos.toByteArray();
+        endTime = System.nanoTime();
+        perfOut("stream InBuf + stream OutBuf: " +
+                (endTime - startTime));
+        match(testOut1, answer);
+
+        // test#2: Non-direct Buffer in + non-direct Buffer out
+        //debugOut("Test#2:\n");
+        //debugOut("inputBuf: " + inBuf + "\n");
+        //debugOut("outputBuf: " + outBuf + "\n");
+
+        startTime = System.nanoTime();
+        cipher.update(inBuf, outBuf);
+        cipher.doFinal(inBuf, outBuf);
+        endTime = System.nanoTime();
+        perfOut("non-direct InBuf + non-direct OutBuf: " +
+                (endTime - startTime));
+        match(outBuf, answer);
+
+        // test#3: Direct Buffer in + direc Buffer out
+        //debugOut("Test#3:\n");
+        //debugOut("(pre) inputBuf: " + inDirectBuf + "\n");
+        //debugOut("(pre) outputBuf: " + outDirectBuf + "\n");
+
+        startTime = System.nanoTime();
+        cipher.update(inDirectBuf, outDirectBuf);
+        cipher.doFinal(inDirectBuf, outDirectBuf);
+        endTime = System.nanoTime();
+        perfOut("direct InBuf + direct OutBuf: " +
+                (endTime - startTime));
+
+        //debugOut("(post) inputBuf: " + inDirectBuf + "\n");
+        //debugOut("(post) outputBuf: " + outDirectBuf + "\n");
+        match(outDirectBuf, answer);
+
+        // test#4: Direct Buffer in + non-direct Buffer out
+        //debugOut("Test#4:\n");
+        inDirectBuf.position(0);
+        outBuf.position(0);
+        //debugOut("inputBuf: " + inDirectBuf + "\n");
+        //debugOut("outputBuf: " + outBuf + "\n");
+
+        startTime = System.nanoTime();
+        cipher.update(inDirectBuf, outBuf);
+        cipher.doFinal(inDirectBuf, outBuf);
+        endTime = System.nanoTime();
+        perfOut("direct InBuf + non-direct OutBuf: " +
+                (endTime - startTime));
+        match(outBuf, answer);
+
+        // test#5: Non-direct Buffer in + direct Buffer out
+        //debugOut("Test#5:\n");
+        inBuf.position(0);
+        outDirectBuf.position(0);
+
+        //debugOut("(pre) inputBuf: " + inBuf + "\n");
+        //debugOut("(pre) outputBuf: " + outDirectBuf + "\n");
+
+        startTime = System.nanoTime();
+        cipher.update(inBuf, outDirectBuf);
+        cipher.doFinal(inBuf, outDirectBuf);
+        endTime = System.nanoTime();
+        perfOut("non-direct InBuf + direct OutBuf: " +
+                (endTime - startTime));
+
+        //debugOut("(post) inputBuf: " + inBuf + "\n");
+        //debugOut("(post) outputBuf: " + outDirectBuf + "\n");
+        match(outDirectBuf, answer);
+
+        debugBuf = null;
+    }
+
+    private static void perfOut(String msg) {
+        if (debugBuf != null) {
+            debugBuf.append("PERF>" + msg);
+        }
+    }
+
+    private static void debugOut(String msg) {
+        if (debugBuf != null) {
+            debugBuf.append(msg);
+        }
+    }
+
+    private static void match(byte[] b1, byte[] b2) throws Exception {
+        if (b1.length != b2.length) {
+            debugOut("got len   : " + b1.length + "\n");
+            debugOut("expect len: " + b2.length + "\n");
+            throw new Exception("mismatch - different length! got: " + b1.length + ", expect: " + b2.length + "\n");
+        } else {
+            for (int i = 0; i < b1.length; i++) {
+                if (b1[i] != b2[i]) {
+                    debugOut("got   : " + toString(b1) + "\n");
+                    debugOut("expect: " + toString(b2) + "\n");
+                    throw new Exception("mismatch");
+                }
+            }
+        }
+    }
+
+    private static void match(ByteBuffer bb, byte[] answer) throws Exception {
+        byte[] bbTemp = new byte[bb.position()];
+        bb.position(0);
+        bb.get(bbTemp, 0, bbTemp.length);
+        match(bbTemp, answer);
+    }
+
+    public static void main(String[] args) throws Exception {
+        main(new TestSymmCiphers());
+    }
+}