Skip to content

Commit 1ee80e0

Browse files
committed
8261355: No data buffering in SunPKCS11 Cipher encryption when the underlying mechanism has no padding
Reviewed-by: valeriep
1 parent d84a7e5 commit 1ee80e0

File tree

2 files changed

+216
-82
lines changed

2 files changed

+216
-82
lines changed

src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java

Lines changed: 112 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ final class P11Cipher extends CipherSpi {
7070
private static interface Padding {
7171
// ENC: format the specified buffer with padding bytes and return the
7272
// actual padding length
73-
int setPaddingBytes(byte[] paddingBuffer, int padLen);
73+
int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen);
7474

7575
// DEC: return the length of trailing padding bytes given the specified
7676
// padded data
@@ -91,8 +91,8 @@ private static class PKCS5Padding implements Padding {
9191
this.blockSize = blockSize;
9292
}
9393

94-
public int setPaddingBytes(byte[] paddingBuffer, int padLen) {
95-
Arrays.fill(paddingBuffer, 0, padLen, (byte) (padLen & 0x007f));
94+
public int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen) {
95+
Arrays.fill(paddingBuffer, startOff, startOff + padLen, (byte) (padLen & 0x007f));
9696
return padLen;
9797
}
9898

@@ -169,6 +169,14 @@ public int unpad(byte[] paddedData, int len)
169169
// specification mandates a fixed size of the key
170170
private int fixedKeySize = -1;
171171

172+
// Indicates whether the underlying PKCS#11 library requires block-sized
173+
// updates during multi-part operations. In such case, we buffer data in
174+
// padBuffer up to a block-size. This may be needed only if padding is
175+
// applied on the Java side. An example of the previous is when the
176+
// CKM_AES_ECB mechanism is used and the PKCS#11 library is NSS. See more
177+
// on JDK-8261355.
178+
private boolean reqBlockUpdates = false;
179+
172180
P11Cipher(Token token, String algorithm, long mechanism)
173181
throws PKCS11Exception, NoSuchAlgorithmException {
174182
super();
@@ -252,6 +260,10 @@ protected void engineSetPadding(String padding)
252260
// no native padding support; use our own padding impl
253261
paddingObj = new PKCS5Padding(blockSize);
254262
padBuffer = new byte[blockSize];
263+
char[] tokenLabel = token.tokenInfo.label;
264+
// NSS requires block-sized updates in multi-part operations.
265+
reqBlockUpdates = ((tokenLabel[0] == 'N' && tokenLabel[1] == 'S'
266+
&& tokenLabel[2] == 'S') ? true : false);
255267
}
256268
} else {
257269
throw new NoSuchPaddingException("Unsupported padding " + padding);
@@ -587,46 +599,54 @@ private int implUpdate(byte[] in, int inOfs, int inLen,
587599
try {
588600
ensureInitialized();
589601
int k = 0;
590-
if (encrypt) {
591-
k = token.p11.C_EncryptUpdate(session.id(), 0, in, inOfs, inLen,
592-
0, out, outOfs, outLen);
593-
} else {
594-
int newPadBufferLen = 0;
595-
if (paddingObj != null) {
596-
if (padBufferLen != 0) {
597-
// NSS throws up when called with data not in multiple
598-
// of blocks. Try to work around this by holding the
599-
// extra data in padBuffer.
600-
if (padBufferLen != padBuffer.length) {
601-
int bufCapacity = padBuffer.length - padBufferLen;
602-
if (inLen > bufCapacity) {
603-
bufferInputBytes(in, inOfs, bufCapacity);
604-
inOfs += bufCapacity;
605-
inLen -= bufCapacity;
606-
} else {
607-
bufferInputBytes(in, inOfs, inLen);
608-
return 0;
609-
}
602+
int newPadBufferLen = 0;
603+
if (paddingObj != null && (!encrypt || reqBlockUpdates)) {
604+
if (padBufferLen != 0) {
605+
if (padBufferLen != padBuffer.length) {
606+
int bufCapacity = padBuffer.length - padBufferLen;
607+
if (inLen > bufCapacity) {
608+
bufferInputBytes(in, inOfs, bufCapacity);
609+
inOfs += bufCapacity;
610+
inLen -= bufCapacity;
611+
} else {
612+
bufferInputBytes(in, inOfs, inLen);
613+
return 0;
610614
}
615+
}
616+
if (encrypt) {
617+
k = token.p11.C_EncryptUpdate(session.id(),
618+
0, padBuffer, 0, padBufferLen,
619+
0, out, outOfs, outLen);
620+
} else {
611621
k = token.p11.C_DecryptUpdate(session.id(),
612622
0, padBuffer, 0, padBufferLen,
613623
0, out, outOfs, outLen);
614-
padBufferLen = 0;
615624
}
616-
newPadBufferLen = inLen & (blockSize - 1);
617-
if (newPadBufferLen == 0) {
618-
newPadBufferLen = padBuffer.length;
619-
}
620-
inLen -= newPadBufferLen;
625+
padBufferLen = 0;
626+
}
627+
newPadBufferLen = inLen & (blockSize - 1);
628+
if (!encrypt && newPadBufferLen == 0) {
629+
// While decrypting with implUpdate, the last encrypted block
630+
// is always held in a buffer. If it's the final one (unknown
631+
// at this point), it may contain padding bytes and need further
632+
// processing. In implDoFinal (where we know it's the final one)
633+
// the buffer is decrypted, unpadded and returned.
634+
newPadBufferLen = padBuffer.length;
621635
}
622-
if (inLen > 0) {
636+
inLen -= newPadBufferLen;
637+
}
638+
if (inLen > 0) {
639+
if (encrypt) {
640+
k += token.p11.C_EncryptUpdate(session.id(), 0, in, inOfs,
641+
inLen, 0, out, (outOfs + k), (outLen - k));
642+
} else {
623643
k += token.p11.C_DecryptUpdate(session.id(), 0, in, inOfs,
624644
inLen, 0, out, (outOfs + k), (outLen - k));
625645
}
626-
// update 'padBuffer' if using our own padding impl.
627-
if (paddingObj != null) {
628-
bufferInputBytes(in, inOfs + inLen, newPadBufferLen);
629-
}
646+
}
647+
// update 'padBuffer' if using our own padding impl.
648+
if (paddingObj != null && newPadBufferLen > 0) {
649+
bufferInputBytes(in, inOfs + inLen, newPadBufferLen);
630650
}
631651
bytesBuffered += (inLen - k);
632652
return k;
@@ -687,60 +707,62 @@ private int implUpdate(ByteBuffer inBuffer, ByteBuffer outBuffer)
687707
}
688708

689709
int k = 0;
690-
if (encrypt) {
691-
if (inAddr == 0 && inArray == null) {
692-
inArray = new byte[inLen];
693-
inBuffer.get(inArray);
694-
} else {
695-
inBuffer.position(origPos + inLen);
696-
}
697-
k = token.p11.C_EncryptUpdate(session.id(),
698-
inAddr, inArray, inOfs, inLen,
699-
outAddr, outArray, outOfs, outLen);
700-
} else {
701-
int newPadBufferLen = 0;
702-
if (paddingObj != null) {
703-
if (padBufferLen != 0) {
704-
// NSS throws up when called with data not in multiple
705-
// of blocks. Try to work around this by holding the
706-
// extra data in padBuffer.
707-
if (padBufferLen != padBuffer.length) {
708-
int bufCapacity = padBuffer.length - padBufferLen;
709-
if (inLen > bufCapacity) {
710-
bufferInputBytes(inBuffer, bufCapacity);
711-
inOfs += bufCapacity;
712-
inLen -= bufCapacity;
713-
} else {
714-
bufferInputBytes(inBuffer, inLen);
715-
return 0;
716-
}
710+
int newPadBufferLen = 0;
711+
if (paddingObj != null && (!encrypt || reqBlockUpdates)) {
712+
if (padBufferLen != 0) {
713+
if (padBufferLen != padBuffer.length) {
714+
int bufCapacity = padBuffer.length - padBufferLen;
715+
if (inLen > bufCapacity) {
716+
bufferInputBytes(inBuffer, bufCapacity);
717+
inOfs += bufCapacity;
718+
inLen -= bufCapacity;
719+
} else {
720+
bufferInputBytes(inBuffer, inLen);
721+
return 0;
717722
}
723+
}
724+
if (encrypt) {
725+
k = token.p11.C_EncryptUpdate(session.id(), 0,
726+
padBuffer, 0, padBufferLen, outAddr, outArray,
727+
outOfs, outLen);
728+
} else {
718729
k = token.p11.C_DecryptUpdate(session.id(), 0,
719730
padBuffer, 0, padBufferLen, outAddr, outArray,
720731
outOfs, outLen);
721-
padBufferLen = 0;
722-
}
723-
newPadBufferLen = inLen & (blockSize - 1);
724-
if (newPadBufferLen == 0) {
725-
newPadBufferLen = padBuffer.length;
726732
}
727-
inLen -= newPadBufferLen;
733+
padBufferLen = 0;
728734
}
729-
if (inLen > 0) {
730-
if (inAddr == 0 && inArray == null) {
731-
inArray = new byte[inLen];
732-
inBuffer.get(inArray);
733-
} else {
734-
inBuffer.position(inBuffer.position() + inLen);
735-
}
735+
newPadBufferLen = inLen & (blockSize - 1);
736+
if (!encrypt && newPadBufferLen == 0) {
737+
// While decrypting with implUpdate, the last encrypted block
738+
// is always held in a buffer. If it's the final one (unknown
739+
// at this point), it may contain padding bytes and need further
740+
// processing. In implDoFinal (where we know it's the final one)
741+
// the buffer is decrypted, unpadded and returned.
742+
newPadBufferLen = padBuffer.length;
743+
}
744+
inLen -= newPadBufferLen;
745+
}
746+
if (inLen > 0) {
747+
if (inAddr == 0 && inArray == null) {
748+
inArray = new byte[inLen];
749+
inBuffer.get(inArray);
750+
} else {
751+
inBuffer.position(inBuffer.position() + inLen);
752+
}
753+
if (encrypt) {
754+
k += token.p11.C_EncryptUpdate(session.id(), inAddr,
755+
inArray, inOfs, inLen, outAddr, outArray,
756+
(outOfs + k), (outLen - k));
757+
} else {
736758
k += token.p11.C_DecryptUpdate(session.id(), inAddr,
737759
inArray, inOfs, inLen, outAddr, outArray,
738760
(outOfs + k), (outLen - k));
739761
}
740-
// update 'padBuffer' if using our own padding impl.
741-
if (paddingObj != null && newPadBufferLen != 0) {
742-
bufferInputBytes(inBuffer, newPadBufferLen);
743-
}
762+
}
763+
// update 'padBuffer' if using our own padding impl.
764+
if (paddingObj != null && newPadBufferLen > 0) {
765+
bufferInputBytes(inBuffer, newPadBufferLen);
744766
}
745767
bytesBuffered += (inLen - k);
746768
if (!(outBuffer instanceof DirectBuffer) &&
@@ -779,10 +801,14 @@ private int implDoFinal(byte[] out, int outOfs, int outLen)
779801
int k = 0;
780802
if (encrypt) {
781803
if (paddingObj != null) {
804+
int startOff = 0;
805+
if (reqBlockUpdates) {
806+
startOff = padBufferLen;
807+
}
782808
int actualPadLen = paddingObj.setPaddingBytes(padBuffer,
783-
requiredOutLen - bytesBuffered);
809+
startOff, requiredOutLen - bytesBuffered);
784810
k = token.p11.C_EncryptUpdate(session.id(),
785-
0, padBuffer, 0, actualPadLen,
811+
0, padBuffer, 0, startOff + actualPadLen,
786812
0, out, outOfs, outLen);
787813
}
788814
// Some implementations such as the NSS Software Token do not
@@ -863,10 +889,14 @@ private int implDoFinal(ByteBuffer outBuffer)
863889

864890
if (encrypt) {
865891
if (paddingObj != null) {
892+
int startOff = 0;
893+
if (reqBlockUpdates) {
894+
startOff = padBufferLen;
895+
}
866896
int actualPadLen = paddingObj.setPaddingBytes(padBuffer,
867-
requiredOutLen - bytesBuffered);
897+
startOff, requiredOutLen - bytesBuffered);
868898
k = token.p11.C_EncryptUpdate(session.id(),
869-
0, padBuffer, 0, actualPadLen,
899+
0, padBuffer, 0, startOff + actualPadLen,
870900
outAddr, outArray, outOfs, outLen);
871901
}
872902
// Some implementations such as the NSS Software Token do not
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (c) 2021, Red Hat, Inc.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8261355
27+
* @library /test/lib ..
28+
* @run main/othervm EncryptionPadding
29+
*/
30+
31+
import java.nio.ByteBuffer;
32+
import java.security.Key;
33+
import java.security.Provider;
34+
import java.util.Arrays;
35+
import javax.crypto.Cipher;
36+
import javax.crypto.spec.SecretKeySpec;
37+
38+
public class EncryptionPadding extends PKCS11Test {
39+
40+
private static String transformation = "AES/ECB/PKCS5Padding";
41+
private static Key key = new SecretKeySpec(new byte[16], "AES");
42+
43+
public static void main(String[] args) throws Exception {
44+
main(new EncryptionPadding(), args);
45+
}
46+
47+
@Override
48+
public void main(Provider p) throws Exception {
49+
testWithInputSize(p, 1);
50+
testWithInputSize(p, 15);
51+
testWithInputSize(p, 16);
52+
testWithInputSize(p, 17);
53+
System.out.println("TEST PASS - OK");
54+
}
55+
56+
private static void testWithInputSize(Provider p, int inputSize)
57+
throws Exception {
58+
testWithInputSize(p, inputSize, false);
59+
testWithInputSize(p, inputSize, true);
60+
}
61+
62+
private static void testWithInputSize(Provider p, int inputSize,
63+
boolean isByteBuffer) throws Exception {
64+
byte[] plainText = new byte[inputSize];
65+
Arrays.fill(plainText, (byte)(inputSize & 0xFF));
66+
ByteBuffer cipherText =
67+
ByteBuffer.allocate(((inputSize / 16 ) + 1) * 16);
68+
byte[] tmp;
69+
70+
Cipher sunPKCS11cipher = Cipher.getInstance(transformation, p);
71+
sunPKCS11cipher.init(Cipher.ENCRYPT_MODE, key);
72+
for (int i = 0; i < ((inputSize - 1) / 16) + 1; i++) {
73+
int updateLength = Math.min(inputSize - (16 * i), 16);
74+
if (!isByteBuffer) {
75+
tmp = sunPKCS11cipher.update(plainText, i * 16,
76+
updateLength);
77+
if (tmp != null) {
78+
cipherText.put(tmp);
79+
}
80+
} else {
81+
ByteBuffer bb = ByteBuffer.allocate(updateLength);
82+
bb.put(plainText, i * 16, updateLength);
83+
bb.flip();
84+
sunPKCS11cipher.update(bb, cipherText);
85+
}
86+
}
87+
if (!isByteBuffer) {
88+
tmp = sunPKCS11cipher.doFinal();
89+
if (tmp != null) {
90+
cipherText.put(tmp);
91+
}
92+
} else {
93+
sunPKCS11cipher.doFinal(ByteBuffer.allocate(0), cipherText);
94+
}
95+
96+
Cipher sunJCECipher = Cipher.getInstance(transformation, "SunJCE");
97+
sunJCECipher.init(Cipher.DECRYPT_MODE, key);
98+
byte[] sunJCEPlain = sunJCECipher.doFinal(cipherText.array());
99+
100+
if (!Arrays.equals(plainText, sunJCEPlain)) {
101+
throw new Exception("Cross-provider cipher test failed.");
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)