1/* 2 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved. 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 24import java.nio.ByteBuffer; 25import java.security.AlgorithmParameters; 26import java.security.Provider; 27import java.security.Security; 28import javax.crypto.SecretKey; 29import javax.crypto.Cipher; 30import javax.crypto.KeyGenerator; 31import javax.crypto.spec.GCMParameterSpec; 32 33/* 34 * @test 35 * @bug 8048596 36 * @summary Check if AEAD operations work correctly when buffers used 37 * for storing plain text and cipher text are overlapped or the same 38 */ 39public class SameBuffer { 40 41 private static final String PROVIDER = "SunJCE"; 42 private static final String AES = "AES"; 43 private static final String GCM = "GCM"; 44 private static final String PADDING = "NoPadding"; 45 private static final int OFFSET = 2; 46 private static final int OFFSETS = 4; 47 private static final int KEY_LENGTHS[] = { 128, 192, 256 }; 48 private static final int TEXT_LENGTHS[] = { 0, 1024 }; 49 private static final int AAD_LENGTHS[] = { 0, 1024 }; 50 51 private final Provider provider; 52 private final SecretKey key; 53 private final String transformation; 54 private final int textLength; 55 private final int AADLength; 56 57 /** 58 * Constructor of the test 59 * 60 * @param provider security provider 61 * @param keyStrength key length 62 * @param textLength length of data 63 * @param AADLength AAD length 64 */ 65 public SameBuffer(Provider provider, String algorithm, String mode, 66 String padding, int keyStrength, int textLength, int AADLength) 67 throws Exception { 68 69 // init a secret key 70 KeyGenerator kg = KeyGenerator.getInstance(algorithm, provider); 71 kg.init(keyStrength); 72 key = kg.generateKey(); 73 74 this.transformation = algorithm + "/" + mode + "/" + padding; 75 this.provider = provider; 76 this.textLength = textLength; 77 this.AADLength = AADLength; 78 } 79 80 public static void main(String[] args) throws Exception { 81 Provider p = Security.getProvider(PROVIDER); 82 for (int keyLength : KEY_LENGTHS) { 83 for (int textLength : TEXT_LENGTHS) { 84 for (int AADLength : AAD_LENGTHS) { 85 for (int i = 0; i < OFFSETS; i++) { 86 // try different offsets 87 int offset = i * OFFSET; 88 runTest(p, AES, GCM, PADDING, keyLength, textLength, 89 AADLength, offset); 90 } 91 } 92 } 93 } 94 } 95 96 /* 97 * Run single test case with given parameters 98 */ 99 static void runTest(Provider p, String algo, String mode, 100 String padding, int keyLength, int textLength, int AADLength, 101 int offset) throws Exception { 102 System.out.println("Testing " + keyLength + " key length; " 103 + textLength + " text lenght; " + AADLength + " AAD length; " 104 + offset + " offset"); 105 if (keyLength > Cipher.getMaxAllowedKeyLength(algo)) { 106 // skip this if this key length is larger than what's 107 // configured in the jce jurisdiction policy files 108 return; 109 } 110 SameBuffer test = new SameBuffer(p, algo, mode, 111 padding, keyLength, textLength, AADLength); 112 113 /* 114 * There are four test cases: 115 * 1. AAD and text are placed in separated byte arrays 116 * 2. AAD and text are placed in the same byte array 117 * 3. AAD and text are placed in separated byte buffers 118 * 4. AAD and text are placed in the same byte buffer 119 */ 120 Cipher ci = test.createCipher(Cipher.ENCRYPT_MODE, null); 121 AlgorithmParameters params = ci.getParameters(); 122 test.doTestWithSeparateArrays(offset, params); 123 test.doTestWithSameArrays(offset, params); 124 test.doTestWithSeparatedBuffer(offset, params); 125 test.doTestWithSameBuffer(offset, params); 126 } 127 128 /* 129 * Run the test in case when AAD and text are placed in separated byte 130 * arrays. 131 */ 132 private void doTestWithSeparateArrays(int offset, 133 AlgorithmParameters params) throws Exception { 134 // prepare buffers to test 135 Cipher c = createCipher(Cipher.ENCRYPT_MODE, params); 136 int outputLength = c.getOutputSize(textLength); 137 int outputBufSize = outputLength + offset * 2; 138 139 byte[] inputText = Helper.generateBytes(outputBufSize); 140 byte[] AAD = Helper.generateBytes(AADLength); 141 142 // do the test 143 runGCMWithSeparateArray(Cipher.ENCRYPT_MODE, AAD, inputText, offset * 2, 144 textLength, offset, params); 145 int tagLength = c.getParameters() 146 .getParameterSpec(GCMParameterSpec.class).getTLen() / 8; 147 runGCMWithSeparateArray(Cipher.DECRYPT_MODE, AAD, inputText, offset, 148 textLength + tagLength, offset, params); 149 } 150 151 /** 152 * Run the test in case when AAD and text are placed in the same byte 153 * array. 154 */ 155 private void doTestWithSameArrays(int offset, AlgorithmParameters params) 156 throws Exception { 157 // prepare buffers to test 158 Cipher c = createCipher(Cipher.ENCRYPT_MODE, params); 159 int outputLength = c.getOutputSize(textLength); 160 int outputBufSize = AADLength + outputLength + offset * 2; 161 162 byte[] AAD_and_text = Helper.generateBytes(outputBufSize); 163 164 // do the test 165 runGCMWithSameArray(Cipher.ENCRYPT_MODE, AAD_and_text, AADLength + offset, 166 textLength, params); 167 int tagLength = c.getParameters() 168 .getParameterSpec(GCMParameterSpec.class).getTLen() / 8; 169 runGCMWithSameArray(Cipher.DECRYPT_MODE, AAD_and_text, AADLength + offset, 170 textLength + tagLength, params); 171 } 172 173 /* 174 * Run the test in case when AAD and text are placed in separated ByteBuffer 175 */ 176 private void doTestWithSeparatedBuffer(int offset, 177 AlgorithmParameters params) throws Exception { 178 // prepare AAD byte buffers to test 179 byte[] AAD = Helper.generateBytes(AADLength); 180 ByteBuffer AAD_Buf = ByteBuffer.allocate(AADLength); 181 AAD_Buf.put(AAD, 0, AAD.length); 182 AAD_Buf.flip(); 183 184 // prepare text byte buffer to encrypt/decrypt 185 Cipher c = createCipher(Cipher.ENCRYPT_MODE, params); 186 int outputLength = c.getOutputSize(textLength); 187 int outputBufSize = outputLength + offset; 188 byte[] inputText = Helper.generateBytes(outputBufSize); 189 ByteBuffer plainTextBB = ByteBuffer.allocateDirect(inputText.length); 190 plainTextBB.put(inputText); 191 plainTextBB.position(offset); 192 plainTextBB.limit(offset + textLength); 193 194 // do test 195 runGCMWithSeparateBuffers(Cipher.ENCRYPT_MODE, AAD_Buf, plainTextBB, offset, 196 textLength, params); 197 int tagLength = c.getParameters() 198 .getParameterSpec(GCMParameterSpec.class).getTLen() / 8; 199 plainTextBB.position(offset); 200 plainTextBB.limit(offset + textLength + tagLength); 201 runGCMWithSeparateBuffers(Cipher.DECRYPT_MODE, AAD_Buf, plainTextBB, offset, 202 textLength + tagLength, params); 203 } 204 205 /* 206 * Run the test in case when AAD and text are placed in the same ByteBuffer 207 */ 208 private void doTestWithSameBuffer(int offset, AlgorithmParameters params) 209 throws Exception { 210 // calculate output length 211 Cipher c = createCipher(Cipher.ENCRYPT_MODE, params); 212 int outputLength = c.getOutputSize(textLength); 213 214 // prepare byte buffer contained AAD and plain text 215 int bufSize = AADLength + offset + outputLength; 216 byte[] AAD_and_Text = Helper.generateBytes(bufSize); 217 ByteBuffer AAD_and_Text_Buf = ByteBuffer.allocate(bufSize); 218 AAD_and_Text_Buf.put(AAD_and_Text, 0, AAD_and_Text.length); 219 220 // do test 221 runGCMWithSameBuffer(Cipher.ENCRYPT_MODE, AAD_and_Text_Buf, offset, 222 textLength, params); 223 int tagLength = c.getParameters() 224 .getParameterSpec(GCMParameterSpec.class).getTLen() / 8; 225 AAD_and_Text_Buf.limit(AADLength + offset + textLength + tagLength); 226 runGCMWithSameBuffer(Cipher.DECRYPT_MODE, AAD_and_Text_Buf, offset, 227 textLength + tagLength, params); 228 229 } 230 231 /* 232 * Execute GCM encryption/decryption of a text placed in a byte array. 233 * AAD is placed in the separated byte array. 234 * Data are processed twice: 235 * - in a separately allocated buffer 236 * - in the text buffer 237 * Check if two results are equal 238 */ 239 private void runGCMWithSeparateArray(int mode, byte[] AAD, byte[] text, 240 int txtOffset, int lenght, int offset, AlgorithmParameters params) 241 throws Exception { 242 // first, generate the cipher text at an allocated buffer 243 Cipher cipher = createCipher(mode, params); 244 cipher.updateAAD(AAD); 245 byte[] outputText = cipher.doFinal(text, txtOffset, lenght); 246 247 // new cipher for encrypt operation 248 Cipher anotherCipher = createCipher(mode, params); 249 anotherCipher.updateAAD(AAD); 250 251 // next, generate cipher text again at the same buffer of plain text 252 int myoff = offset; 253 int off = anotherCipher.update(text, txtOffset, lenght, text, myoff); 254 anotherCipher.doFinal(text, myoff + off); 255 256 // check if two resutls are equal 257 if (!isEqual(text, myoff, outputText, 0, outputText.length)) { 258 throw new RuntimeException("Two results not equal, mode:" + mode); 259 } 260 } 261 262 /* 263 * Execute GCM encrption/decryption of a text. The AAD and text to process 264 * are placed in the same byte array. Data are processed twice: 265 * - in a separetly allocated buffer 266 * - in a buffer that shares content of the AAD_and_Text_BA 267 * Check if two results are equal 268 */ 269 private void runGCMWithSameArray(int mode, byte[] array, int txtOffset, 270 int length, AlgorithmParameters params) throws Exception { 271 // first, generate cipher text at an allocated buffer 272 Cipher cipher = createCipher(mode, params); 273 cipher.updateAAD(array, 0, AADLength); 274 byte[] outputText = cipher.doFinal(array, txtOffset, length); 275 276 // new cipher for encrypt operation 277 Cipher anotherCipher = createCipher(mode, params); 278 anotherCipher.updateAAD(array, 0, AADLength); 279 280 // next, generate cipher text again at the same buffer of plain text 281 int off = anotherCipher.update(array, txtOffset, length, 282 array, txtOffset); 283 anotherCipher.doFinal(array, txtOffset + off); 284 285 // check if two results are equal or not 286 if (!isEqual(array, txtOffset, outputText, 0, 287 outputText.length)) { 288 throw new RuntimeException( 289 "Two results are not equal, mode:" + mode); 290 } 291 } 292 293 /* 294 * Execute GCM encryption/decryption of textBB. AAD and text to process are 295 * placed in different byte buffers. Data are processed twice: 296 * - in a separately allocated buffer 297 * - in a buffer that shares content of the textBB 298 * Check if results are equal 299 */ 300 private void runGCMWithSeparateBuffers(int mode, ByteBuffer buffer, 301 ByteBuffer textBB, int txtOffset, int dataLength, 302 AlgorithmParameters params) throws Exception { 303 // take offset into account 304 textBB.position(txtOffset); 305 textBB.mark(); 306 307 // first, generate the cipher text at an allocated buffer 308 Cipher cipher = createCipher(mode, params); 309 cipher.updateAAD(buffer); 310 buffer.flip(); 311 ByteBuffer outBB = ByteBuffer.allocateDirect( 312 cipher.getOutputSize(dataLength)); 313 314 cipher.doFinal(textBB, outBB);// get cipher text in outBB 315 outBB.flip(); 316 317 // restore positions 318 textBB.reset(); 319 320 // next, generate cipher text again in a buffer that shares content 321 Cipher anotherCipher = createCipher(mode, params); 322 anotherCipher.updateAAD(buffer); 323 buffer.flip(); 324 ByteBuffer buf2 = textBB.duplicate(); // buf2 shares textBuf context 325 buf2.limit(txtOffset + anotherCipher.getOutputSize(dataLength)); 326 int dataProcessed2 = anotherCipher.doFinal(textBB, buf2); 327 buf2.position(txtOffset); 328 buf2.limit(txtOffset + dataProcessed2); 329 330 if (!buf2.equals(outBB)) { 331 throw new RuntimeException( 332 "Two results are not equal, mode:" + mode); 333 } 334 } 335 336 /* 337 * Execute GCM encryption/decryption of text. AAD and a text to process are 338 * placed in the same buffer. Data is processed twice: 339 * - in a separately allocated buffer 340 * - in a buffer that shares content of the AAD_and_Text_BB 341 */ 342 private void runGCMWithSameBuffer(int mode, ByteBuffer buffer, 343 int txtOffset, int length, AlgorithmParameters params) 344 throws Exception { 345 346 // allocate a separate buffer 347 Cipher cipher = createCipher(mode, params); 348 ByteBuffer outBB = ByteBuffer.allocateDirect( 349 cipher.getOutputSize(length)); 350 351 // first, generate the cipher text at an allocated buffer 352 buffer.flip(); 353 buffer.limit(AADLength); 354 cipher.updateAAD(buffer); 355 buffer.limit(AADLength + txtOffset + length); 356 buffer.position(AADLength + txtOffset); 357 cipher.doFinal(buffer, outBB); 358 outBB.flip(); // cipher text in outBB 359 360 // next, generate cipherText again in the same buffer 361 Cipher anotherCipher = createCipher(mode, params); 362 buffer.flip(); 363 buffer.limit(AADLength); 364 anotherCipher.updateAAD(buffer); 365 buffer.limit(AADLength + txtOffset + length); 366 buffer.position(AADLength + txtOffset); 367 368 // share textBuf context 369 ByteBuffer buf2 = buffer.duplicate(); 370 buf2.limit(AADLength + txtOffset + anotherCipher.getOutputSize(length)); 371 int dataProcessed2 = anotherCipher.doFinal(buffer, buf2); 372 buf2.position(AADLength + txtOffset); 373 buf2.limit(AADLength + txtOffset + dataProcessed2); 374 375 if (!buf2.equals(outBB)) { 376 throw new RuntimeException( 377 "Two results are not equal, mode:" + mode); 378 } 379 } 380 381 private boolean isEqual(byte[] A, int offsetA, byte[] B, int offsetB, 382 int bytesToCompare) { 383 System.out.println("offsetA: " + offsetA + " offsetB: " + offsetA 384 + " bytesToCompare: " + bytesToCompare); 385 for (int i = 0; i < bytesToCompare; i++) { 386 int setA = i + offsetA; 387 int setB = i + offsetB; 388 if (setA > A.length - 1 || setB > B.length - 1 389 || A[setA] != B[setB]) { 390 return false; 391 } 392 } 393 394 return true; 395 } 396 397 /* 398 * Creates a Cipher object for testing: for encryption it creates new Cipher 399 * based on previously saved parameters (it is prohibited to use the same 400 * Cipher twice for encription during GCM mode), or returns initiated 401 * existing Cipher. 402 */ 403 private Cipher createCipher(int mode, AlgorithmParameters params) 404 throws Exception { 405 Cipher cipher = Cipher.getInstance(transformation, provider); 406 if (Cipher.ENCRYPT_MODE == mode) { 407 // initiate it with the saved parameters 408 if (params != null) { 409 cipher.init(Cipher.ENCRYPT_MODE, key, params); 410 } else { 411 // intiate the cipher and save parameters 412 cipher.init(Cipher.ENCRYPT_MODE, key); 413 } 414 } else if (cipher != null) { 415 cipher.init(Cipher.DECRYPT_MODE, key, params); 416 } else { 417 throw new RuntimeException("Can't create cipher"); 418 } 419 420 return cipher; 421 } 422 423} 424