Skip to content

Commit

Permalink
Use correct output size for AES on z platform
Browse files Browse the repository at this point in the history
When the fast z command is available the AES cipher does not return the
correct output size on platform z.

Platform z uses a different buffer and output size calculation. The
method `engineGetOutputSize` when running on platform z should return
and take this into account.

A new test was added to exercise byte buffers and various combinations
of encryption, decryption, and orders of doFinal and update operations
which can be used to recreate the problem.

Closes IBM#240

Signed-off-by: Jason Katonica <[email protected]>
  • Loading branch information
jasonkatonica committed Oct 17, 2024
1 parent 2c19669 commit 25d34b8
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 14 deletions.
22 changes: 10 additions & 12 deletions src/main/java/com/ibm/crypto/plus/provider/AESCipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,9 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
checkCipherInitialized();

try {
byte[] output = null;
int outputLen = -1;
if (use_z_fast_command) {
output = new byte[engineGetOutputSize2(inputLen)];
outputLen = engineDoFinal(input, inputOffset, inputLen, output, 0);
} else {
output = new byte[engineGetOutputSize(inputLen)];
outputLen = symmetricCipher.doFinal(input, inputOffset, inputLen, output, 0);
}
byte[] output = new byte[engineGetOutputSize(inputLen)];
int outputLen = engineDoFinal(input, inputOffset, inputLen, output, 0);

if (outputLen < output.length) {
byte[] out = Arrays.copyOfRange(output, 0, outputLen);
if (!encrypting) {
Expand Down Expand Up @@ -192,7 +186,11 @@ protected int engineGetKeySize(Key key) throws InvalidKeyException {
@Override
protected int engineGetOutputSize(int inputLen) {
try {
return symmetricCipher.getOutputSize(inputLen);
if (use_z_fast_command) {
return getOutputSizeForZ(inputLen);
} else {
return symmetricCipher.getOutputSize(inputLen);
}
} catch (Exception e) {
throw provider.providerException("Unable to get output size", e);
}
Expand Down Expand Up @@ -352,7 +350,7 @@ protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
byte[] output = null;
int outputLen = -1;
if (use_z_fast_command) {
output = new byte[engineGetOutputSize2(inputLen)];
output = new byte[getOutputSizeForZ(inputLen)];
outputLen = engineUpdate(input, inputOffset, inputLen, output, 0);
} else {
output = new byte[engineGetOutputSize(inputLen)];
Expand Down Expand Up @@ -516,7 +514,7 @@ private void checkCipherInitialized() throws IllegalStateException {
* @param inputLen
* @return
*/
private int engineGetOutputSize2(int inputLen) {
private int getOutputSizeForZ(int inputLen) {
int totalLen = Math.addExact(buffered, inputLen);
if (padding == Padding.NoPadding || !encrypting)
return totalLen;
Expand Down
136 changes: 136 additions & 0 deletions src/test/java/ibm/jceplus/junit/base/BaseTestResetByteBuffer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright IBM Corp. 2024
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution.
*/

package ibm.jceplus.junit.base;

import java.nio.ByteBuffer;
import java.util.stream.Stream;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class BaseTestResetByteBuffer extends BaseTestJunit5 {

Cipher c;
SecretKey key;
ByteBuffer in, out;
byte[] data = new byte[1500];
byte encrypted[];

private static Stream<Arguments> resetByteBufferTestParameters() {
return Stream.of(
Arguments.of("AES/GCM/NoPadding", false, true, true, true),
Arguments.of("AES/GCM/NoPadding", false, true, true, true),
Arguments.of("AES/GCM/NoPadding", false, true, true, false),
Arguments.of("AES/GCM/NoPadding", false, true, true, false),
Arguments.of("AES/GCM/NoPadding", false, true, false, true),
Arguments.of("AES/GCM/NoPadding", false, true, false, true),
Arguments.of("AES/GCM/NoPadding", false, true, false, false),
Arguments.of("AES/GCM/NoPadding", false, true, false, false),
Arguments.of("AES/CBC/PKCS5Padding", true, true, true, true),
Arguments.of("AES/CBC/PKCS5Padding", true, true, true, true),
Arguments.of("AES/CBC/PKCS5Padding", true, true, true, false),
Arguments.of("AES/CBC/PKCS5Padding", true, true, true, false),
Arguments.of("AES/CBC/PKCS5Padding", true, true, false, true),
Arguments.of("AES/CBC/PKCS5Padding", true, true, false, true),
Arguments.of("AES/CBC/PKCS5Padding", true, true, false, false),
Arguments.of("AES/CBC/PKCS5Padding", true, true, false, false),
Arguments.of("AES/CBC/PKCS5Padding", false, true, true, true),
Arguments.of("AES/CBC/PKCS5Padding", false, true, true, true),
Arguments.of("AES/CBC/PKCS5Padding", false, true, true, false),
Arguments.of("AES/CBC/PKCS5Padding", false, true, true, false),
Arguments.of("AES/CBC/PKCS5Padding", false, true, false, true),
Arguments.of("AES/CBC/PKCS5Padding", false, true, false, true),
Arguments.of("AES/CBC/PKCS5Padding", false, true, false, false),
Arguments.of("AES/CBC/PKCS5Padding", false, true, false, false)
);
}

@ParameterizedTest
@MethodSource("resetByteBufferTestParameters")
public void doTestResetByteBuffer(String algo, boolean encrypt, boolean direct, boolean updateFirst, boolean updateSecond) throws Exception {
// Instantiate algorithm and create appropriate key.
c = Cipher.getInstance(algo, getProviderName());
String a[] = algo.split("/");
KeyGenerator kg = KeyGenerator.getInstance(a[0], getProviderName());
key = kg.generateKey();

// Create encrypted data.
c.init(Cipher.ENCRYPT_MODE, key, c.getParameters());
encrypted = new byte[c.getOutputSize(data.length)];
c.doFinal(data, 0, data.length, encrypted, 0);

// Initialize for encryption or decryption using byte buffers.
if (encrypt == true) {
initializeEncrypt(direct);
} else {
initializeDecrypt(direct);
}

// Perform first operation, either an update or dofinal.
if (updateFirst == true) {
doUpdate();
} else {
doFinal();
}

// Perform second operation, either an update or dofinal.
if (updateSecond == true) {
doUpdate();
} else {
doFinal();
}
}

private void initializeDecrypt(boolean direct) throws Exception {
// Allocate ByteBuffer optionally a set of direct ones.
if (direct) {
in = ByteBuffer.allocateDirect(encrypted.length);
out = ByteBuffer.allocateDirect(encrypted.length);
} else {
in = ByteBuffer.allocate(encrypted.length);
out = ByteBuffer.allocate(encrypted.length);
}
in.put(encrypted);
in.flip();
c.init(Cipher.DECRYPT_MODE, key, c.getParameters());
}

private void initializeEncrypt(boolean direct) throws Exception {
// Allocate ByteBuffer optionally a set of direct ones.
if (direct) {
in = ByteBuffer.allocateDirect(data.length);
out = ByteBuffer.allocateDirect(c.getOutputSize(data.length));
} else {
in = ByteBuffer.allocate(data.length);
out = ByteBuffer.allocate(c.getOutputSize(data.length));
}
c.init(Cipher.ENCRYPT_MODE, key, c.getParameters());
}

private void doUpdate() throws Exception {
int updateLen = data.length / 2;
in.limit(updateLen);
c.update(in, out);
in.limit(in.capacity());
c.doFinal(in, out);
in.flip();
out.position(0);
out.limit(out.capacity());
}

private void doFinal() throws Exception {
c.doFinal(in, out);
in.flip();
out.position(0);
out.limit(out.capacity());
}
}
2 changes: 1 addition & 1 deletion src/test/java/ibm/jceplus/junit/openjceplus/TestAll.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
TestHmacSHA512InteropSunJCE.class, TestHmacSHA3_224.class, TestHmacSHA3_256.class,
TestHmacSHA3_384.class, TestHmacSHA3_512.class, TestImplementationClassesExist.class,
TestImplementationClassesFinal.class, TestMD5.class,
TestInvalidArrayIndex.class, TestPublicMethodsToMakeNonPublic.class, TestRSA.class,
TestInvalidArrayIndex.class, TestPublicMethodsToMakeNonPublic.class, TestResetByteBuffer.class, TestRSA.class,
TestRSA_512.class, TestRSA_1024.class, TestRSA_2048.class, TestRSAKey.class, TestRSAPSS.class,
TestRSAPSSInterop.class, TestRSAPSS2.class, TestMiniRSAPSS2.class, TestRSAPSSInterop2.class,
TestRSAPSSInterop3.class, TestRSASignature.class, TestRSASignatureInteropSunRsaSign.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright IBM Corp. 2024
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution.
*/

package ibm.jceplus.junit.openjceplus;

import ibm.jceplus.junit.base.BaseTestResetByteBuffer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;

@TestInstance(Lifecycle.PER_CLASS)
public class TestResetByteBuffer extends BaseTestResetByteBuffer {

@BeforeAll
public void beforeAll() {
Utils.loadProviderTestSuite();
setProviderName(Utils.TEST_SUITE_PROVIDER_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
TestHmacSHA3_224.class, TestHmacSHA3_256.class, TestHmacSHA3_384.class,
TestHmacSHA3_512.class, TestImplementationClassesExist.class,
TestImplementationClassesFinal.class, TestInvalidArrayIndex.class,
TestPublicMethodsToMakeNonPublic.class, TestRSA.class, TestRSA_2048.class, TestRSAKey.class,
TestPublicMethodsToMakeNonPublic.class, TestResetByteBuffer.class, TestRSA.class, TestRSA_2048.class, TestRSAKey.class,
TestRSAPSS.class, TestMiniRSAPSS2.class, TestRSASignature.class,
TestRSASignatureInteropSunRsaSign.class, TestRSASignatureChunkUpdate.class,
TestRSATypeCheckDefault.class, TestSHA1.class, TestSHA224.class, TestSHA256.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright IBM Corp. 2024
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution.
*/

package ibm.jceplus.junit.openjceplusfips;

import ibm.jceplus.junit.base.BaseTestResetByteBuffer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;

@TestInstance(Lifecycle.PER_CLASS)
public class TestResetByteBuffer extends BaseTestResetByteBuffer {

@BeforeAll
public void beforeAll() {
Utils.loadProviderTestSuite();
setProviderName(Utils.TEST_SUITE_PROVIDER_NAME);
}
}

0 comments on commit 25d34b8

Please sign in to comment.