b6bee08125
Reviewed-by: valeriep
424 lines
17 KiB
Java
424 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2007, 2015, Oracle and/or its affiliates. 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
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.security.AlgorithmParameters;
|
|
import java.security.Provider;
|
|
import java.security.Security;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.KeyGenerator;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8048596
|
|
* @summary Check if AEAD operations work correctly when buffers used
|
|
* for storing plain text and cipher text are overlapped or the same
|
|
*/
|
|
public class SameBuffer {
|
|
|
|
private static final String PROVIDER = "SunJCE";
|
|
private static final String AES = "AES";
|
|
private static final String GCM = "GCM";
|
|
private static final String PADDING = "NoPadding";
|
|
private static final int OFFSET = 2;
|
|
private static final int OFFSETS = 4;
|
|
private static final int KEY_LENGTHS[] = { 128, 192, 256 };
|
|
private static final int TEXT_LENGTHS[] = { 0, 1024 };
|
|
private static final int AAD_LENGTHS[] = { 0, 1024 };
|
|
|
|
private final Provider provider;
|
|
private final SecretKey key;
|
|
private final String transformation;
|
|
private final int textLength;
|
|
private final int AADLength;
|
|
|
|
/**
|
|
* Constructor of the test
|
|
*
|
|
* @param provider security provider
|
|
* @param keyStrength key length
|
|
* @param textLength length of data
|
|
* @param AADLength AAD length
|
|
*/
|
|
public SameBuffer(Provider provider, String algorithm, String mode,
|
|
String padding, int keyStrength, int textLength, int AADLength)
|
|
throws Exception {
|
|
|
|
// init a secret key
|
|
KeyGenerator kg = KeyGenerator.getInstance(algorithm, provider);
|
|
kg.init(keyStrength);
|
|
key = kg.generateKey();
|
|
|
|
this.transformation = algorithm + "/" + mode + "/" + padding;
|
|
this.provider = provider;
|
|
this.textLength = textLength;
|
|
this.AADLength = AADLength;
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
Provider p = Security.getProvider(PROVIDER);
|
|
for (int keyLength : KEY_LENGTHS) {
|
|
for (int textLength : TEXT_LENGTHS) {
|
|
for (int AADLength : AAD_LENGTHS) {
|
|
for (int i = 0; i < OFFSETS; i++) {
|
|
// try different offsets
|
|
int offset = i * OFFSET;
|
|
runTest(p, AES, GCM, PADDING, keyLength, textLength,
|
|
AADLength, offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Run single test case with given parameters
|
|
*/
|
|
static void runTest(Provider p, String algo, String mode,
|
|
String padding, int keyLength, int textLength, int AADLength,
|
|
int offset) throws Exception {
|
|
System.out.println("Testing " + keyLength + " key length; "
|
|
+ textLength + " text lenght; " + AADLength + " AAD length; "
|
|
+ offset + " offset");
|
|
if (keyLength > Cipher.getMaxAllowedKeyLength(algo)) {
|
|
// skip this if this key length is larger than what's
|
|
// configured in the jce jurisdiction policy files
|
|
return;
|
|
}
|
|
SameBuffer test = new SameBuffer(p, algo, mode,
|
|
padding, keyLength, textLength, AADLength);
|
|
|
|
/*
|
|
* There are four test cases:
|
|
* 1. AAD and text are placed in separated byte arrays
|
|
* 2. AAD and text are placed in the same byte array
|
|
* 3. AAD and text are placed in separated byte buffers
|
|
* 4. AAD and text are placed in the same byte buffer
|
|
*/
|
|
Cipher ci = test.createCipher(Cipher.ENCRYPT_MODE, null);
|
|
AlgorithmParameters params = ci.getParameters();
|
|
test.doTestWithSeparateArrays(offset, params);
|
|
test.doTestWithSameArrays(offset, params);
|
|
test.doTestWithSeparatedBuffer(offset, params);
|
|
test.doTestWithSameBuffer(offset, params);
|
|
}
|
|
|
|
/*
|
|
* Run the test in case when AAD and text are placed in separated byte
|
|
* arrays.
|
|
*/
|
|
private void doTestWithSeparateArrays(int offset,
|
|
AlgorithmParameters params) throws Exception {
|
|
// prepare buffers to test
|
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
|
|
int outputLength = c.getOutputSize(textLength);
|
|
int outputBufSize = outputLength + offset * 2;
|
|
|
|
byte[] inputText = Helper.generateBytes(outputBufSize);
|
|
byte[] AAD = Helper.generateBytes(AADLength);
|
|
|
|
// do the test
|
|
runGCMWithSeparateArray(Cipher.ENCRYPT_MODE, AAD, inputText, offset * 2,
|
|
textLength, offset, params);
|
|
int tagLength = c.getParameters()
|
|
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
|
|
runGCMWithSeparateArray(Cipher.DECRYPT_MODE, AAD, inputText, offset,
|
|
textLength + tagLength, offset, params);
|
|
}
|
|
|
|
/**
|
|
* Run the test in case when AAD and text are placed in the same byte
|
|
* array.
|
|
*/
|
|
private void doTestWithSameArrays(int offset, AlgorithmParameters params)
|
|
throws Exception {
|
|
// prepare buffers to test
|
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
|
|
int outputLength = c.getOutputSize(textLength);
|
|
int outputBufSize = AADLength + outputLength + offset * 2;
|
|
|
|
byte[] AAD_and_text = Helper.generateBytes(outputBufSize);
|
|
|
|
// do the test
|
|
runGCMWithSameArray(Cipher.ENCRYPT_MODE, AAD_and_text, AADLength + offset,
|
|
textLength, params);
|
|
int tagLength = c.getParameters()
|
|
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
|
|
runGCMWithSameArray(Cipher.DECRYPT_MODE, AAD_and_text, AADLength + offset,
|
|
textLength + tagLength, params);
|
|
}
|
|
|
|
/*
|
|
* Run the test in case when AAD and text are placed in separated ByteBuffer
|
|
*/
|
|
private void doTestWithSeparatedBuffer(int offset,
|
|
AlgorithmParameters params) throws Exception {
|
|
// prepare AAD byte buffers to test
|
|
byte[] AAD = Helper.generateBytes(AADLength);
|
|
ByteBuffer AAD_Buf = ByteBuffer.allocate(AADLength);
|
|
AAD_Buf.put(AAD, 0, AAD.length);
|
|
AAD_Buf.flip();
|
|
|
|
// prepare text byte buffer to encrypt/decrypt
|
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
|
|
int outputLength = c.getOutputSize(textLength);
|
|
int outputBufSize = outputLength + offset;
|
|
byte[] inputText = Helper.generateBytes(outputBufSize);
|
|
ByteBuffer plainTextBB = ByteBuffer.allocateDirect(inputText.length);
|
|
plainTextBB.put(inputText);
|
|
plainTextBB.position(offset);
|
|
plainTextBB.limit(offset + textLength);
|
|
|
|
// do test
|
|
runGCMWithSeparateBuffers(Cipher.ENCRYPT_MODE, AAD_Buf, plainTextBB, offset,
|
|
textLength, params);
|
|
int tagLength = c.getParameters()
|
|
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
|
|
plainTextBB.position(offset);
|
|
plainTextBB.limit(offset + textLength + tagLength);
|
|
runGCMWithSeparateBuffers(Cipher.DECRYPT_MODE, AAD_Buf, plainTextBB, offset,
|
|
textLength + tagLength, params);
|
|
}
|
|
|
|
/*
|
|
* Run the test in case when AAD and text are placed in the same ByteBuffer
|
|
*/
|
|
private void doTestWithSameBuffer(int offset, AlgorithmParameters params)
|
|
throws Exception {
|
|
// calculate output length
|
|
Cipher c = createCipher(Cipher.ENCRYPT_MODE, params);
|
|
int outputLength = c.getOutputSize(textLength);
|
|
|
|
// prepare byte buffer contained AAD and plain text
|
|
int bufSize = AADLength + offset + outputLength;
|
|
byte[] AAD_and_Text = Helper.generateBytes(bufSize);
|
|
ByteBuffer AAD_and_Text_Buf = ByteBuffer.allocate(bufSize);
|
|
AAD_and_Text_Buf.put(AAD_and_Text, 0, AAD_and_Text.length);
|
|
|
|
// do test
|
|
runGCMWithSameBuffer(Cipher.ENCRYPT_MODE, AAD_and_Text_Buf, offset,
|
|
textLength, params);
|
|
int tagLength = c.getParameters()
|
|
.getParameterSpec(GCMParameterSpec.class).getTLen() / 8;
|
|
AAD_and_Text_Buf.limit(AADLength + offset + textLength + tagLength);
|
|
runGCMWithSameBuffer(Cipher.DECRYPT_MODE, AAD_and_Text_Buf, offset,
|
|
textLength + tagLength, params);
|
|
|
|
}
|
|
|
|
/*
|
|
* Execute GCM encryption/decryption of a text placed in a byte array.
|
|
* AAD is placed in the separated byte array.
|
|
* Data are processed twice:
|
|
* - in a separately allocated buffer
|
|
* - in the text buffer
|
|
* Check if two results are equal
|
|
*/
|
|
private void runGCMWithSeparateArray(int mode, byte[] AAD, byte[] text,
|
|
int txtOffset, int lenght, int offset, AlgorithmParameters params)
|
|
throws Exception {
|
|
// first, generate the cipher text at an allocated buffer
|
|
Cipher cipher = createCipher(mode, params);
|
|
cipher.updateAAD(AAD);
|
|
byte[] outputText = cipher.doFinal(text, txtOffset, lenght);
|
|
|
|
// new cipher for encrypt operation
|
|
Cipher anotherCipher = createCipher(mode, params);
|
|
anotherCipher.updateAAD(AAD);
|
|
|
|
// next, generate cipher text again at the same buffer of plain text
|
|
int myoff = offset;
|
|
int off = anotherCipher.update(text, txtOffset, lenght, text, myoff);
|
|
anotherCipher.doFinal(text, myoff + off);
|
|
|
|
// check if two resutls are equal
|
|
if (!isEqual(text, myoff, outputText, 0, outputText.length)) {
|
|
throw new RuntimeException("Two results not equal, mode:" + mode);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute GCM encrption/decryption of a text. The AAD and text to process
|
|
* are placed in the same byte array. Data are processed twice:
|
|
* - in a separetly allocated buffer
|
|
* - in a buffer that shares content of the AAD_and_Text_BA
|
|
* Check if two results are equal
|
|
*/
|
|
private void runGCMWithSameArray(int mode, byte[] array, int txtOffset,
|
|
int length, AlgorithmParameters params) throws Exception {
|
|
// first, generate cipher text at an allocated buffer
|
|
Cipher cipher = createCipher(mode, params);
|
|
cipher.updateAAD(array, 0, AADLength);
|
|
byte[] outputText = cipher.doFinal(array, txtOffset, length);
|
|
|
|
// new cipher for encrypt operation
|
|
Cipher anotherCipher = createCipher(mode, params);
|
|
anotherCipher.updateAAD(array, 0, AADLength);
|
|
|
|
// next, generate cipher text again at the same buffer of plain text
|
|
int off = anotherCipher.update(array, txtOffset, length,
|
|
array, txtOffset);
|
|
anotherCipher.doFinal(array, txtOffset + off);
|
|
|
|
// check if two results are equal or not
|
|
if (!isEqual(array, txtOffset, outputText, 0,
|
|
outputText.length)) {
|
|
throw new RuntimeException(
|
|
"Two results are not equal, mode:" + mode);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute GCM encryption/decryption of textBB. AAD and text to process are
|
|
* placed in different byte buffers. Data are processed twice:
|
|
* - in a separately allocated buffer
|
|
* - in a buffer that shares content of the textBB
|
|
* Check if results are equal
|
|
*/
|
|
private void runGCMWithSeparateBuffers(int mode, ByteBuffer buffer,
|
|
ByteBuffer textBB, int txtOffset, int dataLength,
|
|
AlgorithmParameters params) throws Exception {
|
|
// take offset into account
|
|
textBB.position(txtOffset);
|
|
textBB.mark();
|
|
|
|
// first, generate the cipher text at an allocated buffer
|
|
Cipher cipher = createCipher(mode, params);
|
|
cipher.updateAAD(buffer);
|
|
buffer.flip();
|
|
ByteBuffer outBB = ByteBuffer.allocateDirect(
|
|
cipher.getOutputSize(dataLength));
|
|
|
|
cipher.doFinal(textBB, outBB);// get cipher text in outBB
|
|
outBB.flip();
|
|
|
|
// restore positions
|
|
textBB.reset();
|
|
|
|
// next, generate cipher text again in a buffer that shares content
|
|
Cipher anotherCipher = createCipher(mode, params);
|
|
anotherCipher.updateAAD(buffer);
|
|
buffer.flip();
|
|
ByteBuffer buf2 = textBB.duplicate(); // buf2 shares textBuf context
|
|
buf2.limit(txtOffset + anotherCipher.getOutputSize(dataLength));
|
|
int dataProcessed2 = anotherCipher.doFinal(textBB, buf2);
|
|
buf2.position(txtOffset);
|
|
buf2.limit(txtOffset + dataProcessed2);
|
|
|
|
if (!buf2.equals(outBB)) {
|
|
throw new RuntimeException(
|
|
"Two results are not equal, mode:" + mode);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute GCM encryption/decryption of text. AAD and a text to process are
|
|
* placed in the same buffer. Data is processed twice:
|
|
* - in a separately allocated buffer
|
|
* - in a buffer that shares content of the AAD_and_Text_BB
|
|
*/
|
|
private void runGCMWithSameBuffer(int mode, ByteBuffer buffer,
|
|
int txtOffset, int length, AlgorithmParameters params)
|
|
throws Exception {
|
|
|
|
// allocate a separate buffer
|
|
Cipher cipher = createCipher(mode, params);
|
|
ByteBuffer outBB = ByteBuffer.allocateDirect(
|
|
cipher.getOutputSize(length));
|
|
|
|
// first, generate the cipher text at an allocated buffer
|
|
buffer.flip();
|
|
buffer.limit(AADLength);
|
|
cipher.updateAAD(buffer);
|
|
buffer.limit(AADLength + txtOffset + length);
|
|
buffer.position(AADLength + txtOffset);
|
|
cipher.doFinal(buffer, outBB);
|
|
outBB.flip(); // cipher text in outBB
|
|
|
|
// next, generate cipherText again in the same buffer
|
|
Cipher anotherCipher = createCipher(mode, params);
|
|
buffer.flip();
|
|
buffer.limit(AADLength);
|
|
anotherCipher.updateAAD(buffer);
|
|
buffer.limit(AADLength + txtOffset + length);
|
|
buffer.position(AADLength + txtOffset);
|
|
|
|
// share textBuf context
|
|
ByteBuffer buf2 = buffer.duplicate();
|
|
buf2.limit(AADLength + txtOffset + anotherCipher.getOutputSize(length));
|
|
int dataProcessed2 = anotherCipher.doFinal(buffer, buf2);
|
|
buf2.position(AADLength + txtOffset);
|
|
buf2.limit(AADLength + txtOffset + dataProcessed2);
|
|
|
|
if (!buf2.equals(outBB)) {
|
|
throw new RuntimeException(
|
|
"Two results are not equal, mode:" + mode);
|
|
}
|
|
}
|
|
|
|
private boolean isEqual(byte[] A, int offsetA, byte[] B, int offsetB,
|
|
int bytesToCompare) {
|
|
System.out.println("offsetA: " + offsetA + " offsetB: " + offsetA
|
|
+ " bytesToCompare: " + bytesToCompare);
|
|
for (int i = 0; i < bytesToCompare; i++) {
|
|
int setA = i + offsetA;
|
|
int setB = i + offsetB;
|
|
if (setA > A.length - 1 || setB > B.length - 1
|
|
|| A[setA] != B[setB]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Creates a Cipher object for testing: for encryption it creates new Cipher
|
|
* based on previously saved parameters (it is prohibited to use the same
|
|
* Cipher twice for encription during GCM mode), or returns initiated
|
|
* existing Cipher.
|
|
*/
|
|
private Cipher createCipher(int mode, AlgorithmParameters params)
|
|
throws Exception {
|
|
Cipher cipher = Cipher.getInstance(transformation, provider);
|
|
if (Cipher.ENCRYPT_MODE == mode) {
|
|
// initiate it with the saved parameters
|
|
if (params != null) {
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, params);
|
|
} else {
|
|
// intiate the cipher and save parameters
|
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
|
}
|
|
} else if (cipher != null) {
|
|
cipher.init(Cipher.DECRYPT_MODE, key, params);
|
|
} else {
|
|
throw new RuntimeException("Can't create cipher");
|
|
}
|
|
|
|
return cipher;
|
|
}
|
|
|
|
}
|