diff --git a/src/share/classes/sun/security/pkcs11/P11Cipher.java b/src/share/classes/sun/security/pkcs11/P11Cipher.java index 4fca6f880a81f4d7b040bec7ecf09e35e2f3c1f0..97e0bb5f64b033ced7b3f699a59f1197bc67fced 100644 --- a/src/share/classes/sun/security/pkcs11/P11Cipher.java +++ b/src/share/classes/sun/security/pkcs11/P11Cipher.java @@ -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.*; 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 @@ import static sun.security.pkcs11.wrapper.PKCS11Constants.*; * 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { // 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; - // XXX PKCS#5 not yet implemented - throw new NoSuchPaddingException("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]; + } } else { throw new NoSuchPaddingException("Unsupported padding " + padding); } @@ -175,7 +248,7 @@ final class P11Cipher extends CipherSpi { // 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 @@ final class P11Cipher extends CipherSpi { } 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { } else { if (iv.length != blockSize) { throw new InvalidAlgorithmParameterException - ("IV length must match block size"); + ("IV length must match block size"); } } } @@ -331,63 +405,43 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { } // see JCE spec + @Override protected int engineUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer) throws ShortBufferException { return implUpdate(inBuffer, outBuffer); @@ -422,14 +478,15 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { } // 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 @@ final class P11Cipher extends CipherSpi { } 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 @@ final class P11Cipher extends CipherSpi { 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); - } - bytesProcessed += inLen; - if (!inPosChanged) { - inBuffer.position(inBuffer.position() + inLen); + 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); + } } + 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { 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 @@ final class P11Cipher extends CipherSpi { } // 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 @@ final class P11Cipher extends CipherSpi { } } + 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; + } } diff --git a/src/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/share/classes/sun/security/pkcs11/SunPKCS11.java index c98bbdbb4a738d5559bf4ec6b806c2476447f354..88d8d4b73939b6e2fb76887e49e16d08d74f495e 100644 --- a/src/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -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 @@ public final class SunPKCS11 extends AuthProvider { // 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 diff --git a/test/sun/security/pkcs11/Cipher/TestSymmCiphers.java b/test/sun/security/pkcs11/Cipher/TestSymmCiphers.java new file mode 100644 index 0000000000000000000000000000000000000000..e102606b4d14d4b98801e6cbae077f5cf6dd0a90 --- /dev/null +++ b/test/sun/security/pkcs11/Cipher/TestSymmCiphers.java @@ -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()); + } +}