| /* |
| * 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; |
| } |
| |
| } |