Skip to content

Commit 6d11f72

Browse files
authored
Merge pull request #148 from cconlon/cipherInputOutputFixes
Improve Cipher input validation, output buffer sizing, and update behavior
2 parents 32498c6 + 2f5b0a8 commit 6d11f72

File tree

3 files changed

+483
-23
lines changed

3 files changed

+483
-23
lines changed

README_JCE.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,36 @@ wolfcrypt-jni-X.X.X/lib/signed/release/wolfcrypt-jni.jar
457457
This pre-signed JAR can be used with the JUnit tests, without having to
458458
re-compile the Java source files. To run the JUnit tests against this
459459
JAR file:
460-
460+
461461
$ cd wolfcrypt-jni-X.X.X
462462
$ cp ./lib/signed/release/wolfcrypt-jni.jar ./lib
463463
$ ant test
464464

465+
### Behavior Discrepancies with SunJCE
466+
---------
467+
468+
#### Cipher PKCS5Padding `doFinal()` Output Buffer Size Requirement
469+
470+
When using the Cipher class with PKCS5Padding mode, the output buffer size
471+
required for some calls to `doFinal()` may be larger than SunJCE.
472+
473+
SunJCE has the ability to save the internal AES/3DES algorithm state, do the
474+
decrypt operation to see how much padding was applied, then restore the state
475+
to where it was before the decrypt happened in order to throw a
476+
ShortBufferException back to the user for a precise output buffer size
477+
restriction requirement.
478+
479+
Native wolfSSL does not have the ability to save and restore the internal
480+
AES/3DES state, so wolfJCE has to be more conservative in the output buffer
481+
size it requires for `doFinal()` calls. wolfJCE will require the output buffer
482+
size to be equal to the incoming ciphertext size (plus any buffered data held
483+
internally), which essentially includes the plaintext that will be decrypted
484+
plus the padding bytes.
485+
486+
This descrepancy should not be an issue, since `doFinal()` returns the
487+
actual number of bytes written to the output buffer, so applications can use
488+
that to know the true output size in the output buffer returned.
489+
465490

466491
### Support
467492
---------

src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java

Lines changed: 130 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,45 @@ private boolean isBlockCipher() {
838838
return isBlockCipher;
839839
}
840840

841+
/**
842+
* If a call to update() would be a no-op (or just return byte[0]) for the
843+
* selected cipher type, mode, and buffered data, return true.
844+
*
845+
* This happens in cases like RSA, or AES-GCM/CCM which don't support
846+
* streaming and buffer all data until doFinal() is called.
847+
*
848+
* @param inputSz total size in bytes of data available for processing,
849+
* including input data and buffered data.
850+
*
851+
* @return true if update() would be a no-op, otherwise false
852+
*/
853+
private boolean isNoOpUpdate(int inputSz) {
854+
855+
/* RSA keeps buffered data until final() call */
856+
if (cipherType == CipherType.WC_RSA) {
857+
return true;
858+
}
859+
860+
/* AES-GCM and AES-CCM keep all data buffered until final() call,
861+
* wolfJCE does not support streaming GCM/CCM yet. */
862+
if (cipherType == CipherType.WC_AES &&
863+
(cipherMode == CipherMode.WC_GCM ||
864+
cipherMode == CipherMode.WC_CCM)) {
865+
return true;
866+
}
867+
868+
/* If total data input (plus buffered) is less than block size,
869+
* update() is a no-op, except for CTR and OFB which are stream
870+
* ciphers */
871+
if ((inputSz < blockSize) &&
872+
(cipherMode != CipherMode.WC_CTR) &&
873+
(cipherMode != CipherMode.WC_OFB)) {
874+
return true;
875+
}
876+
877+
return false;
878+
}
879+
841880
private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len)
842881
throws IllegalArgumentException {
843882

@@ -847,8 +886,15 @@ private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len)
847886
byte[] tmpIn = null;
848887
byte[] tmpBuf = null;
849888

850-
if (input == null || len < 0)
851-
throw new IllegalArgumentException("Null input buffer or len < 0");
889+
if (input == null || len < 0 || inputOffset < 0) {
890+
throw new IllegalArgumentException(
891+
"Null input buffer or len/offset < 0");
892+
}
893+
894+
if (input.length < (inputOffset + len)) {
895+
throw new IllegalArgumentException(
896+
"Input buffer length smaller than inputOffset + len");
897+
}
852898

853899
this.operationStarted = true;
854900

@@ -865,36 +911,32 @@ private byte[] wolfCryptUpdate(byte[] input, int inputOffset, int len)
865911
buffered = tmpIn;
866912
}
867913

868-
/* keep buffered data if RSA or data is less than block size, or doing
869-
* AES-GCM/CCM without stream mode compiled natively, but not for
870-
* CTR/OFB which are stream ciphers */
871-
if ((cipherType == CipherType.WC_RSA) ||
872-
((cipherType == CipherType.WC_AES) &&
873-
(cipherMode == CipherMode.WC_GCM ||
874-
cipherMode == CipherMode.WC_CCM)) ||
875-
((buffered.length < blockSize) &&
876-
(cipherMode != CipherMode.WC_CTR) &&
877-
(cipherMode != CipherMode.WC_OFB))) {
914+
/* Some algos/modes keep data buffered until the doFinal() call, like
915+
* RSA or AES-GCM/CCM without stream mode compiled natively. Just
916+
* return an empty byte array in those cases here. */
917+
if (isNoOpUpdate(len + buffered.length)) {
878918
return new byte[0];
879919
}
880920

881-
/* calculate blocks and partial non-block size remaining */
882-
blocks = buffered.length / blockSize;
921+
/* Calculate blocks and partial non-block size remaining */
922+
blocks = buffered.length / blockSize;
883923
bytesToProcess = blocks * blockSize;
884924

885925
/* CTR and OFB are stream ciphers, process all available data */
886926
if (cipherMode == CipherMode.WC_CTR ||
887927
cipherMode == CipherMode.WC_OFB) {
888928
bytesToProcess = buffered.length;
889929
}
890-
/* if PKCS#5/7 padding, and decrypting, hold on to last block for
930+
931+
/* If PKCS#5/7 padding, and decrypting, hold on to last block for
891932
* padding check in wolfCryptFinal() */
892933
else if (paddingType == PaddingType.WC_PKCS5 &&
893-
direction == OpMode.WC_DECRYPT) {
934+
direction == OpMode.WC_DECRYPT &&
935+
bytesToProcess > 0) {
894936
bytesToProcess -= blockSize;
895937
}
896938

897-
/* not enough data to process yet return until more or final */
939+
/* Not enough data to process yet return until more or final */
898940
if (bytesToProcess == 0) {
899941
return new byte[0];
900942
}
@@ -1207,6 +1249,43 @@ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen)
12071249
return output;
12081250
}
12091251

1252+
/**
1253+
* Sanity check output buffer size is large enough for update() call,
1254+
* based on padding and buffered data.
1255+
*
1256+
* @param inputSz size of input data to update()
1257+
* @param outputSz total size of output buffer provided
1258+
*
1259+
* @throws ShortBufferException if output buffer is too small
1260+
*/
1261+
private void checkUpdateOutputBufferSize(int inputSz, int outputSz)
1262+
throws ShortBufferException {
1263+
1264+
int outSize;
1265+
1266+
if (!isNoOpUpdate(inputSz)) {
1267+
outSize = engineGetOutputSize(inputSz);
1268+
1269+
/* update() in DECRYPT mode with PKCS5 padding will hold
1270+
* back one block of data for padding check in final() */
1271+
if (direction == OpMode.WC_DECRYPT &&
1272+
paddingType == PaddingType.WC_PKCS5) {
1273+
if (outSize % blockSize == 0) {
1274+
outSize -= blockSize;
1275+
}
1276+
else {
1277+
outSize -= (outSize % blockSize);
1278+
}
1279+
}
1280+
1281+
if (outputSz < outSize) {
1282+
throw new ShortBufferException(
1283+
"Output buffer too small, need " + outSize +
1284+
" bytes, got " + outputSz);
1285+
}
1286+
}
1287+
}
1288+
12101289
@Override
12111290
protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
12121291
byte[] output, int outputOffset)
@@ -1219,8 +1298,21 @@ protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
12191298
"Cipher has not been initialized yet");
12201299
}
12211300

1222-
log("update (in offset: " + inputOffset + ", len: " +
1223-
inputLen + ", out offset: " + outputOffset + ")");
1301+
log("update (inputOffset: " + inputOffset + ", inputLen: " +
1302+
inputLen + ", outputOffset: " + outputOffset + ")");
1303+
1304+
if (output == null || (output.length < outputOffset)) {
1305+
throw new IllegalArgumentException(
1306+
"output is null or offset past output array sz");
1307+
}
1308+
1309+
if (input == null || (inputLen + inputOffset > input.length)) {
1310+
throw new IllegalArgumentException(
1311+
"input is null or inOffset + inputLen past input array size");
1312+
}
1313+
1314+
/* Sanitize output buffer size, throws ShortBufferException if needed */
1315+
checkUpdateOutputBufferSize(inputLen, output.length - outputOffset);
12241316

12251317
tmpOut = wolfCryptUpdate(input, inputOffset, inputLen);
12261318
if (tmpOut == null) {
@@ -1278,9 +1370,25 @@ protected int engineDoFinal(byte[] input, int inputOffset,
12781370
"Cipher has not been initialized yet");
12791371
}
12801372

1281-
log("final (in offset: " + inputOffset + ", len: " +
1282-
inputLen + ", out offset: " + outputOffset +
1283-
", buffered: " + buffered.length + ")");
1373+
log("final (inputOffset: " + inputOffset + ", inputLen: " +
1374+
inputLen + ", outputOffset: " + outputOffset + ", buffered: " +
1375+
buffered.length + ")");
1376+
1377+
if (output == null || (outputOffset > output.length)) {
1378+
throw new IllegalArgumentException(
1379+
"output is null or offset past output array sz");
1380+
}
1381+
1382+
/* SunJCE can save Cipher state so it can throw a more precise
1383+
* ShortBufferException after checking the actual length after
1384+
* stripping padding. But, native wolfCrypt does not support
1385+
* saving/restoring Aes state, so we err on the side of making callers
1386+
* give us up to the next block size of output space */
1387+
if ((output.length - outputOffset) < engineGetOutputSize(inputLen)) {
1388+
throw new ShortBufferException("Output buffer too small, need " +
1389+
engineGetOutputSize(inputLen) + " bytes, got " +
1390+
(output.length - outputOffset));
1391+
}
12841392

12851393
tmpOut = wolfCryptFinal(input, inputOffset, inputLen);
12861394

0 commit comments

Comments
 (0)