From cc1915b3b320d9729772a81642cf5ca6563094cd Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Wed, 2 Dec 2020 23:10:32 +0000 Subject: [PATCH] 8253821: Improve ByteBuffer performance with GCM Reviewed-by: xuelei, valeriep --- .../com/sun/crypto/provider/AESCipher.java | 41 +- .../com/sun/crypto/provider/CipherCore.java | 59 +- .../sun/crypto/provider/FeedbackCipher.java | 24 + .../classes/com/sun/crypto/provider/GCTR.java | 114 ++- .../com/sun/crypto/provider/GHASH.java | 33 + .../crypto/provider/GaloisCounterMode.java | 475 +++++++++- .../provider/Cipher/AEAD/GCMBufferTest.java | 851 ++++++++++++++++++ .../Cipher/AEAD/GCMIncrementByte4.java | 43 + .../Cipher/AEAD/GCMIncrementDirect4.java | 41 + .../Cipher/AEAD/OverlapByteBuffer.java | 159 ++++ .../provider/Cipher/AEAD/SameBuffer.java | 11 + .../provider/Cipher/AES/TestKATForGCM.java | 239 +++-- .../TextLength/SameBufferOverwrite.java | 128 +++ .../CipherByteBufferOverwriteTest.java | 58 +- .../ssl/SSLSession/CheckSessionContext.java | 21 + 15 files changed, 2137 insertions(+), 160 deletions(-) create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/TextLength/SameBufferOverwrite.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java index e7669109e1294..d0f9f9886b1d4 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java @@ -25,12 +25,21 @@ package com.sun.crypto.provider; -import java.security.*; -import java.security.spec.*; -import javax.crypto.*; -import javax.crypto.spec.*; import javax.crypto.BadPaddingException; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; /** * This class implements the AES algorithm in its various modes @@ -411,6 +420,7 @@ protected int engineUpdate(byte[] input, int inputOffset, int inputLen, outputOffset); } + /** * Encrypts or decrypts data in a single-part operation, * or finishes a multiple-part operation. @@ -641,5 +651,26 @@ protected void engineUpdateAAD(ByteBuffer src) { } } } -} + /** + * Finalize crypto operation with ByteBuffers + * + * @param input the input ByteBuffer + * @param output the output ByteBuffer + * + * @return output length + * @throws ShortBufferException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + @Override + protected int engineDoFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + if (core.getMode() == CipherCore.GCM_MODE && !input.hasArray()) { + return core.gcmDoFinal(input, output); + } else { + return super.engineDoFinal(input, output); + } + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java index ddee737674a34..c804bcc00b2c6 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java @@ -25,6 +25,7 @@ package com.sun.crypto.provider; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Locale; @@ -722,8 +723,7 @@ int update(byte[] input, int inputOffset, int inputLen, byte[] output, len = (len > 0 ? (len - (len % unitBytes)) : 0); // check output buffer capacity - if ((output == null) || - ((output.length - outputOffset) < len)) { + if (output == null || (output.length - outputOffset) < len) { throw new ShortBufferException("Output buffer must be " + "(at least) " + len + " bytes long"); @@ -917,10 +917,10 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int estOutSize = getOutputSizeByOperation(inputLen, true); int outputCapacity = checkOutputCapacity(output, outputOffset, estOutSize); - int offset = decrypting ? 0 : outputOffset; // 0 for decrypting + int offset = outputOffset; byte[] finalBuf = prepareInputBuffer(input, inputOffset, inputLen, output, outputOffset); - byte[] outWithPadding = null; // for decrypting only + byte[] internalOutput = null; // for decrypting only int finalOffset = (finalBuf == input) ? inputOffset : 0; int finalBufLen = (finalBuf == input) ? inputLen : finalBuf.length; @@ -934,11 +934,14 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, if (outputCapacity < estOutSize) { cipher.save(); } - // create temporary output buffer so that only "real" - // data bytes are passed to user's output buffer. - outWithPadding = new byte[estOutSize]; + if (getMode() != GCM_MODE || outputCapacity < estOutSize) { + // create temporary output buffer if the estimated size is larger + // than the user-provided buffer. + internalOutput = new byte[estOutSize]; + offset = 0; + } } - byte[] outBuffer = decrypting ? outWithPadding : output; + byte[] outBuffer = (internalOutput != null) ? internalOutput : output; int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer, offset, finalBufLen, input); @@ -954,9 +957,11 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + " bytes needed"); } // copy the result into user-supplied output buffer - System.arraycopy(outWithPadding, 0, output, outputOffset, outLen); - // decrypt mode. Zero out output data that's not required - Arrays.fill(outWithPadding, (byte) 0x00); + if (internalOutput != null) { + System.arraycopy(internalOutput, 0, output, outputOffset, outLen); + // decrypt mode. Zero out output data that's not required + Arrays.fill(internalOutput, (byte) 0x00); + } } endDoFinal(); return outLen; @@ -970,16 +975,15 @@ private void endDoFinal() { } } - private int unpad(int outLen, byte[] outWithPadding) + private int unpad(int outLen, int off, byte[] outWithPadding) throws BadPaddingException { - int padStart = padding.unpad(outWithPadding, 0, outLen); + int padStart = padding.unpad(outWithPadding, off, outLen); if (padStart < 0) { throw new BadPaddingException("Given final block not " + "properly padded. Such issues can arise if a bad key " + "is used during decryption."); } - outLen = padStart; - return outLen; + return padStart - off; } private byte[] prepareInputBuffer(byte[] input, int inputOffset, @@ -1055,7 +1059,7 @@ private int fillOutputBuffer(byte[] finalBuf, int finalOffset, len = finalNoPadding(finalBuf, finalOffset, output, outOfs, finalBufLen); if (decrypting && padding != null) { - len = unpad(len, output); + len = unpad(len, outOfs, output); } return len; } finally { @@ -1225,4 +1229,27 @@ void updateAAD(byte[] src, int offset, int len) { checkReinit(); cipher.updateAAD(src, offset, len); } + + // This must only be used with GCM. + // If some data has been buffered from an update call, operate on the buffer + // then run doFinal. + int gcmDoFinal(ByteBuffer src, ByteBuffer dst) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + int estOutSize = getOutputSizeByOperation(src.remaining(), true); + if (estOutSize > dst.remaining()) { + throw new ShortBufferException("output buffer too small"); + } + + if (decrypting) { + if (buffered > 0) { + cipher.decrypt(buffer, 0, buffered, new byte[0], 0); + } + return cipher.decryptFinal(src, dst); + } else { + if (buffered > 0) { + ((GaloisCounterMode)cipher).encrypt(buffer, 0, buffered); + } + return cipher.encryptFinal(src, dst); + } + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java index a6d46adf0e391..c7bb57664a153 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/FeedbackCipher.java @@ -25,6 +25,7 @@ package com.sun.crypto.provider; +import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.InvalidAlgorithmParameterException; import javax.crypto.*; @@ -242,4 +243,27 @@ int getBufferedLength() { // internally during decryption mode return 0; } + + /* + * ByteBuffer methods should not be accessed as CipherCore and AESCipher + * copy the data to byte arrays. These methods are to satisfy the compiler. + */ + int encrypt(ByteBuffer src, ByteBuffer dst) { + throw new UnsupportedOperationException("ByteBuffer not supported"); + }; + + int decrypt(ByteBuffer src, ByteBuffer dst) { + throw new UnsupportedOperationException("ByteBuffer not supported"); + }; + + int encryptFinal(ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException, ShortBufferException { + throw new UnsupportedOperationException("ByteBuffer not supported"); + }; + + int decryptFinal(ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException, AEADBadTagException, + ShortBufferException { + throw new UnsupportedOperationException("ByteBuffer not supported"); + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java index 60d26083b7fed..1a09a617a4d45 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GCTR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2020, 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 @@ -54,11 +54,15 @@ */ final class GCTR extends CounterMode { + // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy + private static final int MAX_LEN = 1024; + GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) { super(cipher); if (initialCounterBlk.length != AES_BLOCK_SIZE) { - throw new RuntimeException("length of initial counter block (" + initialCounterBlk.length + - ") not equal to AES_BLOCK_SIZE (" + AES_BLOCK_SIZE + ")"); + throw new RuntimeException("length of initial counter block (" + + initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" + + AES_BLOCK_SIZE + ")"); } iv = initialCounterBlk; @@ -112,9 +116,89 @@ int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { } } + // input must be multiples of AES blocks, 128-bit, when calling update + int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) { + if (inLen - inOfs > in.length) { + throw new RuntimeException("input length out of bound"); + } + if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) { + throw new RuntimeException("input length unsupported"); + } + // See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for + // details on the check for 'dst' having enough space for the result. + + long blocksLeft = blocksUntilRollover(); + int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE; + if (numOfCompleteBlocks >= blocksLeft) { + // Counter Mode encryption cannot be used because counter will + // roll over incorrectly. Use GCM-specific code instead. + byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; + for (int i = 0; i < numOfCompleteBlocks; i++) { + embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); + for (int n = 0; n < AES_BLOCK_SIZE; n++) { + int index = (i * AES_BLOCK_SIZE + n); + dst.put((byte) ((in[inOfs + index] ^ encryptedCntr[n]))); + } + GaloisCounterMode.increment32(counter); + } + return inLen; + } else { + int len = inLen - inLen % AES_BLOCK_SIZE; + int processed = len; + byte[] out = new byte[Math.min(MAX_LEN, len)]; + int offset = inOfs; + while (processed > MAX_LEN) { + encrypt(in, offset, MAX_LEN, out, 0); + dst.put(out, 0, MAX_LEN); + processed -= MAX_LEN; + offset += MAX_LEN; + } + encrypt(in, offset, processed, out, 0); + // If dst is less than blocksize, insert only what it can. Extra + // bytes would cause buffers with enough size to fail with a + // short buffer + dst.put(out, 0, Math.min(dst.remaining(), processed)); + return len; + } + } + + // input operates on multiples of AES blocks, 128-bit, when calling update. + // The remainder is left in the src buffer. + int update(ByteBuffer src, ByteBuffer dst) { + long blocksLeft = blocksUntilRollover(); + int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE; + if (numOfCompleteBlocks >= blocksLeft) { + // Counter Mode encryption cannot be used because counter will + // roll over incorrectly. Use GCM-specific code instead. + byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; + for (int i = 0; i < numOfCompleteBlocks; i++) { + embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); + for (int n = 0; n < AES_BLOCK_SIZE; n++) { + dst.put((byte) (src.get() ^ encryptedCntr[n])); + } + GaloisCounterMode.increment32(counter); + } + return numOfCompleteBlocks * AES_BLOCK_SIZE; + } + + int len = src.remaining() - (src.remaining() % AES_BLOCK_SIZE); + int processed = len; + byte[] in = new byte[Math.min(MAX_LEN, len)]; + while (processed > MAX_LEN) { + src.get(in, 0, MAX_LEN); + encrypt(in, 0, MAX_LEN, in, 0); + dst.put(in, 0, MAX_LEN); + processed -= MAX_LEN; + } + src.get(in, 0, processed); + encrypt(in, 0, processed, in, 0); + dst.put(in, 0, processed); + return len; + } + // input can be arbitrary size when calling doFinal int doFinal(byte[] in, int inOfs, int inLen, byte[] out, - int outOfs) throws IllegalBlockSizeException { + int outOfs) throws IllegalBlockSizeException { try { if (inLen < 0) { throw new IllegalBlockSizeException("Negative input size!"); @@ -130,7 +214,7 @@ int doFinal(byte[] in, int inOfs, int inLen, byte[] out, for (int n = 0; n < lastBlockSize; n++) { out[outOfs + completeBlkLen + n] = (byte) ((in[inOfs + completeBlkLen + n] ^ - encryptedCntr[n])); + encryptedCntr[n])); } } } @@ -139,4 +223,24 @@ int doFinal(byte[] in, int inOfs, int inLen, byte[] out, } return inLen; } + + // src can be arbitrary size when calling doFinal + int doFinal(ByteBuffer src, ByteBuffer dst) { + int len = src.remaining(); + int lastBlockSize = len % AES_BLOCK_SIZE; + try { + update(src, dst); + if (lastBlockSize != 0) { + // do the last partial block + byte[] encryptedCntr = new byte[AES_BLOCK_SIZE]; + embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0); + for (int n = 0; n < lastBlockSize; n++) { + dst.put((byte) (src.get() ^ encryptedCntr[n])); + } + } + } finally { + reset(); + } + return len; + } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java index 7ee114624eeff..e9ce33b6b582b 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GHASH.java @@ -29,6 +29,7 @@ package com.sun.crypto.provider; +import java.nio.ByteBuffer; import java.security.ProviderException; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -198,6 +199,38 @@ void update(byte[] in, int inOfs, int inLen) { processBlocks(in, inOfs, inLen/AES_BLOCK_SIZE, state, subkeyHtbl); } + // Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy + private static final int MAX_LEN = 1024; + + // Will process as many blocks it can and will leave the remaining. + int update(ByteBuffer src, int inLen) { + inLen -= (inLen % AES_BLOCK_SIZE); + if (inLen == 0) { + return 0; + } + + int processed = inLen; + byte[] in = new byte[Math.min(MAX_LEN, inLen)]; + while (processed > MAX_LEN ) { + src.get(in, 0, MAX_LEN); + update(in, 0 , MAX_LEN); + processed -= MAX_LEN; + } + src.get(in, 0, processed); + update(in, 0, processed); + return inLen; + } + + void doLastBlock(ByteBuffer src, int inLen) { + int processed = update(src, inLen); + if (inLen == processed) { + return; + } + byte[] block = new byte[AES_BLOCK_SIZE]; + src.get(block, 0, inLen - processed); + update(block, 0, AES_BLOCK_SIZE); + } + private static void ghashRangeCheck(byte[] in, int inOfs, int inLen, long[] st, long[] subH) { if (inLen < 0) { throw new RuntimeException("invalid input length: " + inLen); diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index 178bdb024a2bc..cde13fff1c247 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -25,13 +25,21 @@ package com.sun.crypto.provider; -import java.util.Arrays; -import java.io.*; -import java.security.*; -import javax.crypto.*; -import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; +import sun.nio.ch.DirectBuffer; import sun.security.util.ArrayUtil; +import javax.crypto.AEADBadTagException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.ProviderException; + +import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE; + /** * This class represents ciphers in GaloisCounter (GCM) mode. @@ -68,9 +76,12 @@ final class GaloisCounterMode extends FeedbackCipher { private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); private int sizeOfAAD = 0; - // buffer for storing input in decryption, not used for encryption + // buffer data for crypto operation private ByteArrayOutputStream ibuffer = null; + // Original dst buffer if there was an overlap situation + private ByteBuffer originalDst = null; + // in bytes; need to convert to bits (default value 128) when needed private int tagLenBytes = DEFAULT_TAG_LEN; @@ -177,8 +188,17 @@ private static byte[] getJ0(byte[] iv, byte[] subkeyH) { return j0; } - private static void checkDataLength(int processed, int len) { - if (processed > MAX_BUF_SIZE - len) { + /** + * Calculate if the given data lengths and the already processed data + * exceeds the maximum allowed processed data by GCM. + * @param lengths lengths of unprocessed data. + */ + private void checkDataLength(int ... lengths) { + int max = MAX_BUF_SIZE; + for (int len : lengths) { + max = Math.subtractExact(max, len); + } + if (processed > max) { throw new ProviderException("SunJCE provider only supports " + "input size up to " + MAX_BUF_SIZE + " bytes"); } @@ -426,6 +446,64 @@ void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs, } } + // Process en/decryption all the way to the last block. It takes both + // For input it takes the ibuffer which is wrapped in 'buffer' and 'src' + // from doFinal. + void doLastBlock(ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException { + + if (buffer != null && buffer.remaining() > 0) { + // en/decrypt on how much buffer there is in AES_BLOCK_SIZE + processed += gctrPAndC.update(buffer, dst); + + // Process the remainder in the buffer + if (buffer.remaining() > 0) { + // Copy the remainder of the buffer into the extra block + byte[] block = new byte[AES_BLOCK_SIZE]; + int over = buffer.remaining(); + int len = over; // how much is processed by in the extra block + buffer.get(block, 0, over); + + // if src is empty, update the final block and wait for later + // to finalize operation + if (src.remaining() > 0) { + // Fill out block with what is in data + if (src.remaining() > AES_BLOCK_SIZE - over) { + src.get(block, over, AES_BLOCK_SIZE - over); + len += AES_BLOCK_SIZE - over; + } else { + // If the remaining in buffer + data does not fill a + // block, complete the ghash operation + int l = src.remaining(); + src.get(block, over, l); + len += l; + } + } + gctrPAndC.update(block, 0, AES_BLOCK_SIZE, dst); + processed += len; + } + } + + // en/decrypt whatever remains in src. + // If src has been consumed, this will be a no-op + processed += gctrPAndC.doFinal(src, dst); + } + + /* + * This method is for CipherCore to insert the remainder of its buffer + * into the ibuffer before a doFinal(ByteBuffer, ByteBuffer) operation + */ + int encrypt(byte[] in, int inOfs, int len) { + if (len > 0) { + // store internally until encryptFinal + ArrayUtil.nullAndBoundsCheck(in, inOfs, len); + if (ibuffer == null) { + ibuffer = new ByteArrayOutputStream(); + } + ibuffer.write(in, inOfs, len); + } + return len; + } /** * Performs encryption operation. @@ -436,32 +514,93 @@ void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs, * * @param in the buffer with the input data to be encrypted * @param inOfs the offset in in - * @param len the length of the input data + * @param inLen the length of the input data * @param out the buffer for the result * @param outOfs the offset in out - * @exception ProviderException if len is not - * a multiple of the block size * @return the number of bytes placed into the out buffer */ - int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { - ArrayUtil.blockSizeCheck(len, blockSize); - - checkDataLength(processed, len); + int encrypt(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { + checkDataLength(inLen, getBufferedLength()); + ArrayUtil.nullAndBoundsCheck(in, inOfs, inLen); + ArrayUtil.nullAndBoundsCheck(out, outOfs, inLen); processAAD(); + // 'inLen' stores the length to use with buffer 'in'. + // 'len' stores the length returned by the method. + int len = inLen; + + // if there is enough data in the ibuffer and 'in', encrypt it. + if (ibuffer != null && ibuffer.size() > 0) { + byte[] buffer = ibuffer.toByteArray(); + // number of bytes not filling a block + int remainder = ibuffer.size() % blockSize; + // number of bytes along block boundary + int blen = ibuffer.size() - remainder; + + // If there is enough bytes in ibuffer for a block or more, + // encrypt that first. + if (blen > 0) { + encryptBlocks(buffer, 0, blen, out, outOfs); + outOfs += blen; + } - if (len > 0) { - ArrayUtil.nullAndBoundsCheck(in, inOfs, len); - ArrayUtil.nullAndBoundsCheck(out, outOfs, len); + // blen is now the offset for 'buffer' + + // Construct and encrypt a block if there is enough 'buffer' and + // 'in' to make one + if ((inLen + remainder) >= blockSize) { + byte[] block = new byte[blockSize]; + + System.arraycopy(buffer, blen, block, 0, remainder); + int inLenUsed = blockSize - remainder; + System.arraycopy(in, inOfs, block, remainder, inLenUsed); + + encryptBlocks(block, 0, blockSize, out, outOfs); + inOfs += inLenUsed; + inLen -= inLenUsed; + len += (blockSize - inLenUsed); + outOfs += blockSize; + ibuffer.reset(); + // Code below will write the remainder from 'in' to ibuffer + } else if (remainder > 0) { + // If a block or more was encrypted from 'buffer' only, but the + // rest of 'buffer' with 'in' could not construct a block, then + // put the rest of 'buffer' back into ibuffer. + ibuffer.reset(); + ibuffer.write(buffer, blen, remainder); + // Code below will write the remainder from 'in' to ibuffer + } + // If blen == 0 and there was not enough to construct a block + // from 'buffer' and 'in', then let the below code append 'in' to + // the ibuffer. + } + + // Write any remaining bytes outside the blockSize into ibuffer. + int remainder = inLen % blockSize; + if (remainder > 0) { + if (ibuffer == null) { + ibuffer = new ByteArrayOutputStream(inLen % blockSize); + } + len -= remainder; + inLen -= remainder; + // remainder offset is based on original buffer length + ibuffer.write(in, inOfs + inLen, remainder); + } - gctrPAndC.update(in, inOfs, len, out, outOfs); - processed += len; - ghashAllToS.update(out, outOfs, len); + // Encrypt the remaining blocks inside of 'in' + if (inLen > 0) { + encryptBlocks(in, inOfs, inLen, out, outOfs); } return len; } + void encryptBlocks(byte[] in, int inOfs, int len, byte[] out, int outOfs) { + gctrPAndC.update(in, inOfs, len, out, outOfs); + processed += len; + ghashAllToS.update(out, outOfs, len); + } + /** * Performs encryption operation for the last time. * @@ -474,10 +613,8 @@ int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { */ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs) throws IllegalBlockSizeException, ShortBufferException { - if (len > MAX_BUF_SIZE - tagLenBytes) { - throw new ShortBufferException - ("Can't fit both data and tag into one buffer"); - } + checkDataLength(len, getBufferedLength(), tagLenBytes); + try { ArrayUtil.nullAndBoundsCheck(out, outOfs, (len + tagLenBytes)); @@ -485,8 +622,6 @@ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs) throw new ShortBufferException("Output buffer too small"); } - checkDataLength(processed, len); - processAAD(); if (len > 0) { ArrayUtil.nullAndBoundsCheck(in, inOfs, len); @@ -494,15 +629,45 @@ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs) doLastBlock(in, inOfs, len, out, outOfs, true); } - byte[] lengthBlock = - getLengthBlock(sizeOfAAD, processed); - ghashAllToS.update(lengthBlock); - byte[] s = ghashAllToS.digest(); - byte[] sOut = new byte[s.length]; + byte[] block = getLengthBlock(sizeOfAAD, processed); + ghashAllToS.update(block); + block = ghashAllToS.digest(); GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); - gctrForSToTag.doFinal(s, 0, s.length, sOut, 0); + gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0); + + System.arraycopy(block, 0, out, (outOfs + len), tagLenBytes); + return (len + tagLenBytes); + } + + int encryptFinal(ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException, ShortBufferException { + dst = overlapDetection(src, dst); + int len = src.remaining(); + len += getBufferedLength(); + + // 'len' includes ibuffer data + checkDataLength(len, tagLenBytes); + dst.mark(); + if (dst.remaining() < len + tagLenBytes) { + throw new ShortBufferException("Output buffer too small"); + } + + processAAD(); + if (len > 0) { + doLastBlock((ibuffer == null || ibuffer.size() == 0) ? + null : ByteBuffer.wrap(ibuffer.toByteArray()), src, dst); + dst.reset(); + ghashAllToS.doLastBlock(dst, len); + } + + byte[] block = getLengthBlock(sizeOfAAD, processed); + ghashAllToS.update(block); + block = ghashAllToS.digest(); + GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); + gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0); + dst.put(block, 0, tagLenBytes); + restoreDst(dst); - System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes); return (len + tagLenBytes); } @@ -524,10 +689,6 @@ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs) * @return the number of bytes placed into the out buffer */ int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { - ArrayUtil.blockSizeCheck(len, blockSize); - - checkDataLength(ibuffer.size(), len); - processAAD(); if (len > 0) { @@ -540,6 +701,19 @@ int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { return 0; } + int decrypt(ByteBuffer src, ByteBuffer dst) { + if (src.remaining() > 0) { + byte[] b = new byte[src.remaining()]; + src.get(b); + try { + ibuffer.write(b); + } catch (IOException e) { + throw new ProviderException("Unable to add remaining input to the buffer", e); + } + } + return 0; + } + /** * Performs decryption operation for the last time. * @@ -566,11 +740,11 @@ int decryptFinal(byte[] in, int inOfs, int len, // do this check here can also catch the potential integer overflow // scenario for the subsequent output buffer capacity check. - checkDataLength(ibuffer.size(), (len - tagLenBytes)); + checkDataLength(getBufferedLength(), (len - tagLenBytes)); try { ArrayUtil.nullAndBoundsCheck(out, outOfs, - (ibuffer.size() + len) - tagLenBytes); + (getBufferedLength() + len) - tagLenBytes); } catch (ArrayIndexOutOfBoundsException aiobe) { throw new ShortBufferException("Output buffer too small"); } @@ -586,7 +760,7 @@ int decryptFinal(byte[] in, int inOfs, int len, // If decryption is in-place or there is buffered "ibuffer" data, copy // the "in" byte array into the ibuffer before proceeding. - if (in == out || ibuffer.size() > 0) { + if (in == out || getBufferedLength() > 0) { if (len > 0) { ibuffer.write(in, inOfs, len); } @@ -602,19 +776,16 @@ int decryptFinal(byte[] in, int inOfs, int len, doLastBlock(in, inOfs, len, out, outOfs, false); } - byte[] lengthBlock = - getLengthBlock(sizeOfAAD, processed); - ghashAllToS.update(lengthBlock); - - byte[] s = ghashAllToS.digest(); - byte[] sOut = new byte[s.length]; + byte[] block = getLengthBlock(sizeOfAAD, processed); + ghashAllToS.update(block); + block = ghashAllToS.digest(); GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); - gctrForSToTag.doFinal(s, 0, s.length, sOut, 0); + gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0); // check entire authentication tag for time-consistency int mismatch = 0; for (int i = 0; i < tagLenBytes; i++) { - mismatch |= tag[i] ^ sOut[i]; + mismatch |= tag[i] ^ block[i]; } if (mismatch != 0) { @@ -624,6 +795,122 @@ int decryptFinal(byte[] in, int inOfs, int len, return len; } + // Note: In-place operations do not need an intermediary copy because + // the GHASH check was performed before the decryption. + int decryptFinal(ByteBuffer src, ByteBuffer dst) + throws IllegalBlockSizeException, AEADBadTagException, + ShortBufferException { + + dst = overlapDetection(src, dst); + // Length of the input + ByteBuffer tag; + ByteBuffer ct = src.duplicate(); + + ByteBuffer buffer = ((ibuffer == null || ibuffer.size() == 0) ? null : + ByteBuffer.wrap(ibuffer.toByteArray())); + int len; + + if (ct.remaining() >= tagLenBytes) { + tag = src.duplicate(); + tag.position(ct.limit() - tagLenBytes); + ct.limit(ct.limit() - tagLenBytes); + len = ct.remaining(); + if (buffer != null) { + len += buffer.remaining(); + } + } else if (buffer != null && ct.remaining() < tagLenBytes) { + // It's unlikely the tag will be between the buffer and data + tag = ByteBuffer.allocate(tagLenBytes); + int limit = buffer.remaining() - (tagLenBytes - ct.remaining()); + buffer.mark(); + buffer.position(limit); + // Read from "new" limit to buffer's end + tag.put(buffer); + // reset buffer to data only + buffer.reset(); + buffer.limit(limit); + tag.put(ct); + tag.flip(); + // Limit is how much of the ibuffer has been chopped off. + len = buffer.remaining(); + } else { + throw new AEADBadTagException("Input too short - need tag"); + } + + // 'len' contains the length in ibuffer and src + checkDataLength(len); + + if (len > dst.remaining()) { + throw new ShortBufferException("Output buffer too small"); + } + + processAAD(); + // Set the mark for a later reset. Either it will be zero, or the tag + // buffer creation above will have consume some or all of it. + ct.mark(); + + // If there is data stored in the buffer + if (buffer != null && buffer.remaining() > 0) { + ghashAllToS.update(buffer, buffer.remaining()); + // Process the overage + if (buffer.remaining() > 0) { + // Fill out block between two buffers + if (ct.remaining() > 0) { + int over = buffer.remaining(); + byte[] block = new byte[AES_BLOCK_SIZE]; + // Copy the remainder of the buffer into the extra block + buffer.get(block, 0, over); + + // Fill out block with what is in data + if (ct.remaining() > AES_BLOCK_SIZE - over) { + ct.get(block, over, AES_BLOCK_SIZE - over); + ghashAllToS.update(block, 0, AES_BLOCK_SIZE); + } else { + // If the remaining in buffer + data does not fill a + // block, complete the ghash operation + int l = ct.remaining(); + ct.get(block, over, l); + ghashAllToS.doLastBlock(ByteBuffer.wrap(block), over + l); + } + } else { + // data is empty, so complete the ghash op with the + // remaining buffer + ghashAllToS.doLastBlock(buffer, buffer.remaining()); + } + } + // Prepare buffer for decryption + buffer.flip(); + } + + if (ct.remaining() > 0) { + ghashAllToS.doLastBlock(ct, ct.remaining()); + } + // Prepare buffer for decryption if available + ct.reset(); + + byte[] block = getLengthBlock(sizeOfAAD, len); + ghashAllToS.update(block); + block = ghashAllToS.digest(); + GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); + gctrForSToTag.doFinal(block, 0, tagLenBytes, block, 0); + + // check entire authentication tag for time-consistency + int mismatch = 0; + for (int i = 0; i < tagLenBytes; i++) { + mismatch |= tag.get() ^ block[i]; + } + + if (mismatch != 0) { + throw new AEADBadTagException("Tag mismatch!"); + } + + // Decrypt the all the input data and put it into dst + doLastBlock(buffer, ct, dst); + restoreDst(dst); + // 'processed' from the gctr decryption operation, not ghash + return processed; + } + // return tag length in bytes int getTagLen() { return this.tagLenBytes; @@ -636,4 +923,94 @@ int getBufferedLength() { return ibuffer.size(); } } + + /** + * Check for overlap. If the src and dst buffers are using shared data and + * if dst will overwrite src data before src can be processed. If so, make + * a copy to put the dst data in. + */ + ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) { + if (src.isDirect() && dst.isDirect()) { + DirectBuffer dsrc = (DirectBuffer) src; + DirectBuffer ddst = (DirectBuffer) dst; + + // Get the current memory address for the given ByteBuffers + long srcaddr = dsrc.address(); + long dstaddr = ddst.address(); + + // Find the lowest attachment that is the base memory address of the + // shared memory for the src object + while (dsrc.attachment() != null) { + srcaddr = ((DirectBuffer) dsrc.attachment()).address(); + dsrc = (DirectBuffer) dsrc.attachment(); + } + + // Find the lowest attachment that is the base memory address of the + // shared memory for the dst object + while (ddst.attachment() != null) { + dstaddr = ((DirectBuffer) ddst.attachment()).address(); + ddst = (DirectBuffer) ddst.attachment(); + } + + // If the base addresses are not the same, there is no overlap + if (srcaddr != dstaddr) { + return dst; + } + // At this point we know these objects share the same memory. + // This checks the starting position of the src and dst address for + // overlap. + // It uses the base address minus the passed object's address to get + // the offset from the base address, then add the position() from + // the passed object. That gives up the true offset from the base + // address. As long as the src side is >= the dst side, we are not + // in overlap. + if (((DirectBuffer) src).address() - srcaddr + src.position() >= + ((DirectBuffer) dst).address() - dstaddr + dst.position()) { + return dst; + } + + } else if (!src.isDirect() && !dst.isDirect()) { + if (!src.isReadOnly()) { + // If using the heap, check underlying byte[] address. + if (!src.array().equals(dst.array()) ) { + return dst; + } + + // Position plus arrayOffset() will give us the true offset from + // the underlying byte[] address. + if (src.position() + src.arrayOffset() >= + dst.position() + dst.arrayOffset()) { + return dst; + } + } + } else { + // buffer types aren't the same + return dst; + } + + // Create a copy + ByteBuffer tmp = dst.duplicate(); + // We can use a heap buffer for internal use, save on alloc cost + ByteBuffer bb = ByteBuffer.allocate(dst.remaining()); + tmp.limit(dst.limit()); + tmp.position(dst.position()); + bb.put(tmp); + bb.flip(); + originalDst = dst; + return bb; + } + + /** + * If originalDst exists, dst is an internal dst buffer, then copy the data + * into the original dst buffer + */ + void restoreDst(ByteBuffer dst) { + if (originalDst == null) { + return; + } + + dst.flip(); + originalDst.put(dst); + originalDst = null; + } } diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java new file mode 100644 index 0000000000000..54d271d63ae9b --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMBufferTest.java @@ -0,0 +1,851 @@ +/* + * Copyright (c) 2020, 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. + */ + +/* + * @test + * @summary Use Cipher update and doFinal with a mixture of byte[], bytebuffer, + * and offset while verifying return values. Also using different and + * in-place buffers. + * + * in-place is not tested with different buffer types as it is not a logical + * scenario and is complicated by getOutputSize calculations. + */ + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class GCMBufferTest implements Cloneable { + + // Data type for the operation + enum dtype { BYTE, HEAP, DIRECT }; + // Data map + static HashMap> datamap = new HashMap<>(); + // List of enum values for order of operation + List ops; + + static final int AESBLOCK = 16; + // The remaining input data length is inserted at the particular index + // in sizes[] during execution. + static final int REMAINDER = -1; + + String algo; + boolean same = true; + int[] sizes; + boolean incremental = false; + // In some cases the theoretical check is too complicated to verify + boolean theoreticalCheck; + List dataSet; + int inOfs = 0, outOfs = 0; + + static class Data { + int id; + SecretKey key; + byte[] iv; + byte[] pt; + byte[] aad; + byte[] ct; + byte[] tag; + + Data(String keyalgo, int id, String key, String iv, byte[] pt, String aad, + String ct, String tag) { + this.id = id; + this.key = new SecretKeySpec(HexToBytes(key), keyalgo); + this.iv = HexToBytes(iv); + this.pt = pt; + this.aad = HexToBytes(aad); + this.ct = HexToBytes(ct); + this.tag = HexToBytes(tag); + } + + Data(String keyalgo, int id, String key, String iv, String pt, String aad, + String ct, String tag) { + this(keyalgo, id, key, iv, HexToBytes(pt), aad, ct, tag); + } + + Data(String keyalgo, int id, String key, int ptlen) { + this.id = id; + this.key = new SecretKeySpec(HexToBytes(key), keyalgo); + iv = new byte[16]; + pt = new byte[ptlen]; + tag = new byte[12]; + aad = new byte[0]; + byte[] tct = null; + try { + SecureRandom r = new SecureRandom(); + r.nextBytes(iv); + r.nextBytes(pt); + Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, this.key, + new GCMParameterSpec(tag.length * 8, this.iv)); + tct = c.doFinal(pt); + } catch (Exception e) { + System.out.println("Error in generating data for length " + + ptlen); + } + ct = new byte[ptlen]; + System.arraycopy(tct, 0, ct, 0, ct.length); + System.arraycopy(tct, ct.length, tag, 0, tag.length); + } + + } + + /** + * Construct a test with an algorithm and a list of dtype. + * @param algo Algorithm string + * @param ops List of dtypes. If only one dtype is specified, only a + * doFinal operation will occur. If multiple dtypes are + * specified, the last is a doFinal, the others are updates. + */ + GCMBufferTest(String algo, List ops) { + this.algo = algo; + this.ops = ops; + theoreticalCheck = true; + dataSet = datamap.get(algo); + } + + public GCMBufferTest clone() throws CloneNotSupportedException{ + return (GCMBufferTest)super.clone(); + } + + /** + * Define particular data sizes to be tested. "REMAINDER", which has a + * value of -1, can be used to insert the remaining input text length at + * that index during execution. + * @param sizes Data sizes for each dtype in the list. + */ + GCMBufferTest dataSegments(int[] sizes) { + this.sizes = sizes; + return this; + } + + /** + * Do not perform in-place operations + */ + GCMBufferTest differentBufferOnly() { + this.same = false; + return this; + } + + /** + * Enable incrementing through each data size available. This can only be + * used when the List has more than one dtype entry. + */ + GCMBufferTest incrementalSegments() { + this.incremental = true; + return this; + } + + /** + * Specify a particular test dataset. + * + * @param id id value for the test data to used in this test. + */ + GCMBufferTest dataSet(int id) throws Exception { + for (Data d : datamap.get(algo)) { + if (d.id == id) { + dataSet = List.of(d); + return this; + } + } + throw new Exception("Unaeble to find dataSet id = " + id); + } + + /** + * Set both input and output offsets to the same offset + * @param offset value for inOfs and outOfs + * @return + */ + GCMBufferTest offset(int offset) { + this.inOfs = offset; + this.outOfs = offset; + return this; + } + + /** + * Set the input offset + * @param offset value for input offset + * @return + */ + GCMBufferTest inOfs(int offset) { + this.inOfs = offset; + return this; + } + + /** + * Set the output offset + * @param offset value for output offset + * @return + */ + GCMBufferTest outOfs(int offset) { + this.outOfs = offset; + return this; + } + + /** + * Reverse recursive loop that starts at the end-1 index, going to 0, in + * the size array to calculate all the possible sizes. + * It returns the remaining data size not used in the loop. This remainder + * is used for the end index which is the doFinal op. + */ + int inc(int index, int max, int total) { + if (sizes[index] == max - total) { + sizes[index + 1]++; + total++; + sizes[index] = 0; + } else if (index == 0) { + sizes[index]++; + } + + total += sizes[index]; + if (index > 0) { + return inc(index - 1, max, total); + } + return total; + } + + // Call recursive loop and take returned remainder value for last index + boolean incrementSizes(int max) { + sizes[ops.size() - 1] = max - inc(ops.size() - 2, max, 0); + if (sizes[ops.size() - 2] == max) { + // We are at the end, exit test loop + return false; + } + return true; + } + + void test() throws Exception { + int i = 1; + System.out.println("Algo: " + algo + " \tOps: " + ops.toString()); + for (Data data : dataSet) { + + // If incrementalSegments is enabled, run through that test only + if (incremental) { + if (ops.size() < 2) { + throw new Exception("To do incrementalSegments you must" + + "have more that 1 dtype in the list"); + } + sizes = new int[ops.size()]; + + while (incrementSizes(data.pt.length)) { + System.out.print("Encrypt: Data Index: " + i + " \tSizes[ "); + for (int v : sizes) { + System.out.print(v + " "); + } + System.out.println("]"); + encrypt(data); + } + Arrays.fill(sizes, 0); + + while (incrementSizes(data.ct.length + data.tag.length)) { + System.out.print("Decrypt: Data Index: " + i + " \tSizes[ "); + for (int v : sizes) { + System.out.print(v + " "); + } + System.out.println("]"); + decrypt(data); + } + + } else { + // Default test of 0 and 2 offset doing in place and different + // i/o buffers + System.out.println("Encrypt: Data Index: " + i); + encrypt(data); + + System.out.println("Decrypt: Data Index: " + i); + decrypt(data); + } + i++; + } + } + + // Setup data for encryption + void encrypt(Data data) throws Exception { + byte[] input, output; + + input = data.pt; + output = new byte[data.ct.length + data.tag.length]; + System.arraycopy(data.ct, 0, output, 0, data.ct.length); + System.arraycopy(data.tag, 0, output, data.ct.length, + data.tag.length); + + // Test different input/output buffers + System.out.println("\tinput len: " + input.length + " inOfs " + + inOfs + " outOfs " + outOfs + " in/out buffer: different"); + crypto(true, data, input, output); + + // Test with in-place buffers + if (same) { + System.out.println("\tinput len: " + input.length + " inOfs " + + inOfs + " outOfs " + outOfs + " in/out buffer: in-place"); + cryptoSameBuffer(true, data, input, output); + } + } + + // Setup data for decryption + void decrypt(Data data) throws Exception { + byte[] input, output; + + input = new byte[data.ct.length + data.tag.length]; + System.arraycopy(data.ct, 0, input, 0, data.ct.length); + System.arraycopy(data.tag, 0, input, data.ct.length, data.tag.length); + output = data.pt; + + // Test different input/output buffers + System.out.println("\tinput len: " + input.length + " inOfs " + + inOfs + " outOfs " + outOfs + " in-place: different"); + crypto(false, data, input, output); + + // Test with in-place buffers + if (same) { + System.out.println("\tinput len: " + input.length + " inOfs " + + inOfs + " outOfs " + outOfs + " in-place: same"); + cryptoSameBuffer(false, data, input, output); + } + } + + /** + * Perform cipher operation using different input and output buffers. + * This method allows mixing of data types (byte, heap, direct). + */ + void crypto(boolean encrypt, Data d, byte[] input, byte[] output) + throws Exception { + byte[] pt = new byte[input.length + inOfs]; + System.arraycopy(input, 0, pt, inOfs, input.length); + byte[] expectedOut = new byte[output.length + outOfs]; + System.arraycopy(output, 0, expectedOut, outOfs, output.length); + int plen = input.length / ops.size(); // partial input length + int theoreticallen;// expected output length + int dataoffset = 0; // offset of unconsumed data in pt + int index = 0; // index of which op we are on + int rlen; // result length + int pbuflen = 0; // plen remaining in the GCM internal buffers + + Cipher cipher = Cipher.getInstance(algo); + cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), + d.key, new GCMParameterSpec(d.tag.length * 8, d.iv)); + cipher.updateAAD(d.aad); + + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ba.write(new byte[outOfs], 0, outOfs); + for (dtype v : ops) { + if (index < ops.size() - 1) { + if (sizes != null && input.length > 0) { + if (sizes[index] == -1) { + plen = input.length - dataoffset; + } else { + if (sizes[index] > input.length) { + plen = input.length; + } else { + plen = sizes[index]; + } + } + } + + int olen = cipher.getOutputSize(plen) + outOfs; + + /* + * The theoretical limit is the length of the data sent to + * update() + any data might be setting in CipherCore or GCM + * internal buffers % the block size. + */ + theoreticallen = (plen + pbuflen) - ((plen + pbuflen) % AESBLOCK); + + // Update operations + switch (v) { + case BYTE -> { + byte[] out = new byte[olen]; + rlen = cipher.update(pt, dataoffset + inOfs, plen, out, + outOfs); + ba.write(out, outOfs, rlen); + } + case HEAP -> { + ByteBuffer b = ByteBuffer.allocate(plen + outOfs); + b.position(outOfs); + b.put(pt, dataoffset + inOfs, plen); + b.flip(); + b.position(outOfs); + ByteBuffer out = ByteBuffer.allocate(olen); + out.position(outOfs); + rlen = cipher.update(b, out); + ba.write(out.array(), outOfs, rlen); + } + case DIRECT -> { + ByteBuffer b = ByteBuffer.allocateDirect(plen + outOfs); + b.position(outOfs); + b.put(pt, dataoffset + inOfs, plen); + b.flip(); + b.position(outOfs); + ByteBuffer out = ByteBuffer.allocateDirect(olen); + out.position(outOfs); + rlen = cipher.update(b, out); + byte[] o = new byte[rlen]; + out.flip(); + out.position(outOfs); + out.get(o, 0, rlen); + ba.write(o); + } + default -> throw new Exception("Unknown op: " + v.name()); + } + + if (theoreticalCheck) { + pbuflen += plen - rlen; + if (encrypt && rlen != theoreticallen) { + throw new Exception("Wrong update return len (" + + v.name() + "): " + "rlen=" + rlen + + ", expected output len=" + theoreticallen); + } + } + + dataoffset += plen; + index++; + + } else { + // doFinal operation + plen = input.length - dataoffset; + + int olen = cipher.getOutputSize(plen) + outOfs; + switch (v) { + case BYTE -> { + byte[] out = new byte[olen]; + rlen = cipher.doFinal(pt, dataoffset + inOfs, + plen, out, outOfs); + ba.write(out, outOfs, rlen); + } + case HEAP -> { + ByteBuffer b = ByteBuffer.allocate(plen + inOfs); + b.limit(b.capacity()); + b.position(inOfs); + b.put(pt, dataoffset + inOfs, plen); + b.flip(); + b.position(inOfs); + ByteBuffer out = ByteBuffer.allocate(olen); + out.limit(out.capacity()); + out.position(outOfs); + rlen = cipher.doFinal(b, out); + ba.write(out.array(), outOfs, rlen); + } + case DIRECT -> { + ByteBuffer b = ByteBuffer.allocateDirect(plen + inOfs); + b.limit(b.capacity()); + b.position(inOfs); + b.put(pt, dataoffset + inOfs, plen); + b.flip(); + b.position(inOfs); + ByteBuffer out = ByteBuffer.allocateDirect(olen); + out.limit(out.capacity()); + out.position(outOfs); + rlen = cipher.doFinal(b, out); + byte[] o = new byte[rlen]; + out.flip(); + out.position(outOfs); + out.get(o, 0, rlen); + ba.write(o); + } + default -> throw new Exception("Unknown op: " + v.name()); + } + + if (theoreticalCheck && rlen != olen - outOfs) { + throw new Exception("Wrong doFinal return len (" + + v.name() + "): " + "rlen=" + rlen + + ", expected output len=" + (olen - outOfs)); + } + + // Verify results + byte[] ctresult = ba.toByteArray(); + if (ctresult.length != expectedOut.length || + Arrays.compare(ctresult, expectedOut) != 0) { + String s = "Ciphertext mismatch (" + v.name() + + "):\nresult (len=" + ctresult.length + "):" + + String.format("%0" + (ctresult.length << 1) + "x", + new BigInteger(1, ctresult)) + + "\nexpected (len=" + output.length + "):" + + String.format("%0" + (output.length << 1) + "x", + new BigInteger(1, output)); + System.err.println(s); + throw new Exception(s); + + } + } + } + } + + /** + * Perform cipher operation using in-place buffers. This method does not + * allow mixing of data types (byte, heap, direct). + * + * Mixing data types makes no sense for in-place operations and would + * greatly complicate the test code. + */ + void cryptoSameBuffer(boolean encrypt, Data d, byte[] input, byte[] output) throws Exception { + + byte[] data, out; + if (encrypt) { + data = new byte[output.length + Math.max(inOfs, outOfs)]; + } else { + data = new byte[input.length + Math.max(inOfs, outOfs)]; + } + + ByteBuffer bbin = null, bbout = null; + System.arraycopy(input, 0, data, inOfs, input.length); + byte[] expectedOut = new byte[output.length + outOfs]; + System.arraycopy(output, 0, expectedOut, outOfs, output.length); + int plen = input.length / ops.size(); // partial input length + int theorticallen = plen - (plen % AESBLOCK); // output length + int dataoffset = 0; + int index = 0; + int rlen = 0; // result length + int len = 0; + + Cipher cipher = Cipher.getInstance(algo); + cipher.init((encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), + d.key, new GCMParameterSpec(d.tag.length * 8, d.iv)); + cipher.updateAAD(d.aad); + + // Prepare data + switch (ops.get(0)) { + case HEAP -> { + bbin = ByteBuffer.wrap(data); + bbin.limit(input.length + inOfs); + bbout = bbin.duplicate(); + } + case DIRECT -> { + bbin = ByteBuffer.allocateDirect(data.length); + bbout = bbin.duplicate(); + bbin.put(data, 0, input.length + inOfs); + bbin.flip(); + } + } + + // Set data limits for bytebuffers + if (bbin != null) { + bbin.position(inOfs); + bbout.limit(output.length + outOfs); + bbout.position(outOfs); + } + + // Iterate through each operation + for (dtype v : ops) { + if (index < ops.size() - 1) { + switch (v) { + case BYTE -> { + rlen = cipher.update(data, dataoffset + inOfs, plen, + data, len + outOfs); + } + case HEAP, DIRECT -> { + theorticallen = bbin.remaining() - + (bbin.remaining() % AESBLOCK); + rlen = cipher.update(bbin, bbout); + } + default -> throw new Exception("Unknown op: " + v.name()); + } + + // Check that the theoretical return value matches the actual. + if (theoreticalCheck && encrypt && rlen != theorticallen) { + throw new Exception("Wrong update return len (" + + v.name() + "): " + "rlen=" + rlen + + ", expected output len=" + theorticallen); + } + + dataoffset += plen; + len += rlen; + index++; + + } else { + // Run doFinal op + plen = input.length - dataoffset; + + switch (v) { + case BYTE -> { + rlen = cipher.doFinal(data, dataoffset + inOfs, + plen, data, len + outOfs); + out = Arrays.copyOfRange(data, 0,len + rlen + outOfs); + } + case HEAP, DIRECT -> { + rlen = cipher.doFinal(bbin, bbout); + bbout.flip(); + out = new byte[bbout.remaining()]; + bbout.get(out); + } + default -> throw new Exception("Unknown op: " + v.name()); + } + len += rlen; + + // Verify results + if (len != output.length || + Arrays.compare(out, 0, len, expectedOut, 0, + output.length) != 0) { + String s = "Ciphertext mismatch (" + v.name() + + "):\nresult (len=" + len + "):\n" + + byteToHex(out) + + "\nexpected (len=" + output.length + "):\n" + + String.format("%0" + (output.length << 1) + "x", + new BigInteger(1, output)); + System.err.println(s); + throw new Exception(s); + } + } + } + } + static void offsetTests(GCMBufferTest t) throws Exception { + t.clone().offset(2).test(); + t.clone().inOfs(2).test(); + // Test not designed for overlap situations + t.clone().outOfs(2).differentBufferOnly().test(); + } + + public static void main(String args[]) throws Exception { + initTest(); + // Test single byte array + new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE))); + // Test update-doFinal with byte arrays + new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE))); + // Test update-update-doFinal with byte arrays + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE))); + + // Test single heap bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP))); + // Test update-doFinal with heap bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.HEAP, dtype.HEAP)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP, dtype.HEAP))); + // Test update-update-doFinal with heap bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.HEAP, dtype.HEAP, dtype.HEAP)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.HEAP, dtype.HEAP, dtype.HEAP))); + + // Test single direct bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.DIRECT)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", List.of(dtype.DIRECT))); + // Test update-doFinal with direct bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.DIRECT)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.DIRECT))); + // Test update-update-doFinal with direct bytebuffer + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)).test(); + offsetTests(new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT))); + + // Test update-update-doFinal with byte arrays and preset data sizes + GCMBufferTest t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).dataSegments( + new int[] { 1, 1, GCMBufferTest.REMAINDER}); + t.clone().test(); + offsetTests(t.clone()); + + // Test update-doFinal with a byte array and a direct bytebuffer + t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.BYTE, dtype.DIRECT)).differentBufferOnly(); + t.clone().test(); + offsetTests(t.clone()); + // Test update-doFinal with a byte array and heap and direct bytebuffer + t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.BYTE, dtype.HEAP, dtype.DIRECT)).differentBufferOnly(); + t.clone().test(); + offsetTests(t.clone()); + // Test update-doFinal with a direct bytebuffer and a byte array. + t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly(); + t.clone().test(); + offsetTests(t.clone()); + + // Test update-doFinal with a direct bytebuffer and a byte array with + // preset data sizes. + t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.BYTE)).differentBufferOnly(). + dataSegments(new int[] { 20, GCMBufferTest.REMAINDER }); + t.clone().test(); + offsetTests(t.clone()); + // Test update-update-doFinal with a direct and heap bytebuffer and a + // byte array with preset data sizes. + t = new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.BYTE, dtype.HEAP)). + differentBufferOnly().dataSet(5). + dataSegments(new int[] { 5000, 1000, GCMBufferTest.REMAINDER }); + t.clone().test(); + offsetTests(t.clone()); + + // Test update-update-doFinal with byte arrays, incrementing through + // every data size combination for the Data set 0 + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.BYTE, dtype.BYTE, dtype.BYTE)).incrementalSegments(). + dataSet(0).test(); + // Test update-update-doFinal with direct bytebuffers, incrementing through + // every data size combination for the Data set 0 + new GCMBufferTest("AES/GCM/NoPadding", + List.of(dtype.DIRECT, dtype.DIRECT, dtype.DIRECT)). + incrementalSegments().dataSet(0).test(); + } + + private static byte[] HexToBytes(String hexVal) { + if (hexVal == null) { + return new byte[0]; + } + byte[] result = new byte[hexVal.length()/2]; + for (int i = 0; i < result.length; i++) { + String byteVal = hexVal.substring(2*i, 2*i +2); + result[i] = Integer.valueOf(byteVal, 16).byteValue(); + } + return result; + } + + private static String byteToHex(byte[] barray) { + StringBuilder s = new StringBuilder(); + for (byte b : barray) { + s.append(String.format("%02x", b)); + } + return s.toString(); + } + + // Test data + static void initTest() { + datamap.put("AES/GCM/NoPadding", List.of( + // GCM KAT + new Data("AES", 0, + "141f1ce91989b07e7eb6ae1dbd81ea5e", + "49451da24bd6074509d3cebc2c0394c972e6934b45a1d91f3ce1d3ca69e19" + + "4aa1958a7c21b6f21d530ce6d2cc5256a3f846b6f9d2f38df0102c4791e5" + + "7df038f6e69085646007df999751e248e06c47245f4cd3b8004585a7470d" + + "ee1690e9d2d63169a58d243c0b57b3e5b4a481a3e4e8c60007094ef3adea" + + "2e8f05dd3a1396f", + "d384305af2388699aa302f510913fed0f2cb63ba42efa8c5c9de2922a2ec" + + "2fe87719dadf1eb0aef212b51e74c9c5b934104a43", + "630cf18a91cc5a6481ac9eefd65c24b1a3c93396bd7294d6b8ba3239517" + + "27666c947a21894a079ef061ee159c05beeb4", + "f4c34e5fbe74c0297313268296cd561d59ccc95bbfcdfcdc71b0097dbd83" + + "240446b28dc088abd42b0fc687f208190ff24c0548", + "dbb93bbb56d0439cd09f620a57687f5d"), + // GCM KAT + new Data("AES", 1, "11754cd72aec309bf52f7687212e8957", + "3c819d9a9bed087615030b65", + (String)null, null, null, + "250327c674aaf477aef2675748cf6971"), + // GCM KAT + new Data("AES", 2, "272f16edb81a7abbea887357a58c1917", + "794ec588176c703d3d2a7a07", + (String)null, null, null, + "b6e6f197168f5049aeda32dafbdaeb"), + // zero'd test data + new Data("AES", 3, "272f16edb81a7abbea887357a58c1917", + "794ec588176c703d3d2a7a07", + new byte[256], null, + "15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" + + "e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" + + "07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" + + "7483d041f2c3612ad5033155984470bdfc64d18df73c2745d92f28461bb09832" + + "33524811321ba87d213692825815dd13f528dba601a3c319cac6be9b48686c23" + + "a0ce23d5062916ea8827bbb243f585e446131489e951354c8ab24661f625c02e" + + "15536c5bb602244e98993ff745f3e523399b2059f0e062d8933fad2366e7e147" + + "510a931282bb0e3f635efe7bf05b1dd715f95f5858261b00735224256b6b3e80", + "08b3593840d4ed005f5234ae062a5c"), + // Random test data + new Data("AES", 4, "272f16edb81a7abbea887357a58c1917", + "794ec588176c703d3d2a7a07", + new byte[2075], null, + "15b461672153270e8ba1e6789f7641c5411f3e642abda731b6086f535c216457" + + "e87305bc59a1ff1f7e1e0bbdf302b75549b136606c67d7e5f71277aeca4bc670" + + "07a98f78e0cfa002ed183e62f07893ad31fe67aad1bb37e15b957a14d145f14f" + + "7483d041f2c3612ad5033155984470bdfc64d18df73c2745d92f28461bb09832" + + "33524811321ba87d213692825815dd13f528dba601a3c319cac6be9b48686c23" + + "a0ce23d5062916ea8827bbb243f585e446131489e951354c8ab24661f625c02e" + + "15536c5bb602244e98993ff745f3e523399b2059f0e062d8933fad2366e7e147" + + "510a931282bb0e3f635efe7bf05b1dd715f95f5858261b00735224256b6b3e80" + + "7364cb53ff6d4e88f928cf67ac70da127718a8a35542efbae9dd7567c818a074" + + "9a0c74bd69014639f59768bc55056d1166ea5523e8c66f9d78d980beb8f0d83b" + + "a9e2c5544b94dc3a1a4b6f0f95f897b010150e89ebcacf0daee3c2793d6501a0" + + "b58b411de273dee987e8e8cf8bb29ef2e7f655b46b55fabf64c6a4295e0d080b" + + "6a570ace90eb0fe0f5b5d878bdd90eddaa1150e4d5a6505b350aac814fe99615" + + "317ecd0516a464c7904011ef5922409c0d65b1e43b69d7c3293a8f7d3e9fbee9" + + "eb91ec0007a7d6f72e64deb675d459c5ba07dcfd58d08e6820b100465e6e04f0" + + "663e310584a00d36d23699c1bffc6afa094c75184fc7cde7ad35909c0f49f2f3" + + "fe1e6d745ab628d74ea56b757047de57ce18b4b3c71e8af31a6fac16189cb0a3" + + "a97a1bea447042ce382fcf726560476d759c24d5c735525ea26a332c2094408e" + + "671c7deb81d5505bbfd178f866a6f3a011b3cfdbe089b4957a790688028dfdf7" + + "9a096b3853f9d0d6d3feef230c7f5f46ffbf7486ebdaca5804dc5bf9d202415e" + + "e0d67b365c2f92a17ea740807e4f0b198b42b54f15faa9dff2c7c35d2cf8d72e" + + "b8f8b18875a2e7b5c43d1e0aa5139c461e8153c7f632895aa46ffe2b134e6a0d" + + "dfbf6a336e709adfe951bd52c4dfc7b07a15fb3888fc35b7e758922f87a104c4" + + "563c5c7839cfe5a7edbdb97264a7c4ebc90367b10cbe09dbf2390767ad7afaa8" + + "8fb46b39d3f55f216d2104e5cf040bf3d39b758bea28e2dbce576c808d17a8eb" + + "e2fd183ef42a774e39119dff1f539efeb6ad15d889dfcb0d54d0d4d4cc03c8d9" + + "aa6c9ebd157f5e7170183298d6a30ada8792dcf793d931e2a1eafccbc63c11c0" + + "c5c5ed60837f30017d693ccb294df392a8066a0594a56954aea7b78a16e9a11f" + + "4a8bc2104070a7319f5fab0d2c4ccad8ec5cd8f47c839179bfd54a7bf225d502" + + "cd0a318752fe763e8c09eb88fa57fc5399ad1f797d0595c7b8afdd23f13603e9" + + "6802192bb51433b7723f4e512bd4f799feb94b458e7f9792f5f9bd6733828f70" + + "a6b7ffbbc0bb7575021f081ec2a0d37fecd7cda2daec9a3a9d9dfe1c8034cead" + + "e4b56b581cc82bd5b74b2b30817967d9da33850336f171a4c68e2438e03f4b11" + + "96da92f01b3b7aeab795180ccf40a4b090b1175a1fc0b67c95f93105c3aef00e" + + "13d76cc402539192274fee703730cd0d1c5635257719cc96cacdbad00c6255e2" + + "bd40c775b43ad09599e84f2c3205d75a6661ca3f151183be284b354ce21457d1" + + "3ba65b9b2cdb81874bd14469c2008b3ddec78f7225ecc710cc70de7912ca6a6d" + + "348168322ab59fdafcf5c833bfa0ad4046f4b6da90e9f263db7079af592eda07" + + "5bf16c6b1a8346da9c292a48bf660860a4fc89eaef40bc132779938eca294569" + + "787c740af2b5a8de7f5e10ac750d1e3d0ef3ed168ba408a676e10b8a20bd4be8" + + "3e8336b45e54481726d73e1bd19f165a98e242aca0d8387f2dd22d02d74e23db" + + "4cef9a523587413e0a44d7e3260019a34d3a6b38426ae9fa4655be338d721970" + + "cb9fe76c073f26f9303093a033022cd2c62b2790bce633ba9026a1c93b6535f1" + + "1882bf5880e511b9e1b0b7d8f23a993aae5fd275faac3a5b4ccaf7c06b0b266a" + + "ee970a1e3a4cd7a41094f516960630534e692545b25a347c30e3f328bba4825f" + + "ed754e5525d846131ecba7ca120a6aeabc7bab9f59c890c80b7e31f9bc741591" + + "55d292433ce9558e104102f2cc63ee267c1c8333e841522707ea6d595cb802b9" + + "61697da77bbc4cb404ea62570ab335ebffa2023730732ac5ddba1c3dbb5be408" + + "3c50aea462c1ffa166d7cc3db4b742b747e81b452db2363e91374dee8c6b40f0" + + "e7fbf50e60eaf5cc5649f6bb553aae772c185026ceb052af088c545330a1ffbf" + + "50615b8c7247c6cd386afd7440654f4e15bcfae0c45442ec814fe88433a9d616" + + "ee6cc3f163f0d3d325526d05f25d3b37ad5eeb3ca77248ad86c9042b16c65554" + + "aebb6ad3e17b981492b13f42c5a5dc088e991da303e5a273fdbb8601aece4267" + + "47b01f6cb972e6da1743a0d7866cf206e95f23c6f8e337c901b9cd34a9a1fbbe" + + "1694f2c26b00dfa4d02c0d54540163e798fbdc9c25f30d6406f5b4c13f7ed619" + + "34e350f4059c13aa5e973307a9e3058917cda96fdd082e9c629ccfb2a9f98d12" + + "5c6e4703a7b0f348f5cdeb63cef2133d1c6c1a087591e0a2bca29d09c6565e66" + + "e91042f83b0e74e60a5d57562c23e2fbcd6599c29d7c19e47cf625c2ce24bb8a" + + "13f8e54041498437eec2cedd1e3d8e57a051baa962c0a62d70264d99c5ee716d" + + "5c8b9078db08c8b2c5613f464198a7aff43f76c5b4612b46a4f1cd2a494386c5" + + "7fd28f3d199f0ba8d8e39116cc7db16ce6188205ee49a9dce3d4fa32ea394919" + + "f6e91ef58b84d00b99596b4306c2d9f432d917bb4ac73384c42ae12adb4920d8" + + "c33a816febcb299dcddf3ec7a8eb6e04cdc90891c6e145bd9fc5f41dc4061a46" + + "9feba38545b64ec8203f386ceef52785619e991d274ae80af7e54af535e0b011" + + "5effdf847472992875e09398457604d04e0bb965db692c0cdcf11a", + "687cc09c89298491deb51061d709af"), + // Randomly generated data at the time of execution. + new Data("AES", 5, "11754cd72aec309bf52f7687212e8957", 12345) + ) + ); + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java new file mode 100644 index 0000000000000..1a92e0f4249e1 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, 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.util.List; + +/* + * @test + * @summary Uses GCMBufferTest to run a long test with incrementing through + * each byte in each byte array + * @run main/manual GCMIncrementByte4 + */ + +public class GCMIncrementByte4 { + + public static void main(String args[]) throws Exception { + GCMBufferTest.initTest(); + new GCMBufferTest("AES/GCM/NoPadding", + List.of(GCMBufferTest.dtype.BYTE, GCMBufferTest.dtype.BYTE, + GCMBufferTest.dtype.BYTE)).incrementalSegments().dataSet(4). + test(); + + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java new file mode 100644 index 0000000000000..3372c7713a125 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, 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.util.List; + +/* + * @test + * @summary Uses GCMBufferTest to run a long test with incrementing through + * each byte in each direct bytebuffer + * @run main/manual GCMIncrementDirect4 + */ + +public class GCMIncrementDirect4 { + + public static void main(String args[]) throws Exception { + new GCMBufferTest("AES/GCM/NoPadding", + List.of(GCMBufferTest.dtype.DIRECT, GCMBufferTest.dtype.DIRECT, + GCMBufferTest.dtype.DIRECT)).incrementalSegments().dataSet(4). + test(); + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java new file mode 100644 index 0000000000000..9e84887c9908d --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/OverlapByteBuffer.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020, 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 javax.crypto.Cipher; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; + +/* + * @test + * @summary This tests overlapping buffers using ByteBuffer.slice() with + * array-backed ByteBuffer, read only array-backed, ByteBuffer, and direct + * ByteBuffer. + */ + +/* + * This tests overlapping buffers created with ByteBuffer.slice(). That is + * when the input and output ByteBuffers have shared memory (use the same + * underlying buffer space, commonly used for in-place crypto). The + * complication is the Cipher object specifies that it must be copy-safe. That + * means the output buffer will not overwrite any input data that has not been + * processed. If the output buffer's position or offset is greater than the + * input's overwriting will occur. + */ + +public class OverlapByteBuffer { + + public static void main(String[] args) throws Exception { + byte[] baseBuf = new byte[8192]; + ByteBuffer output, input, in; + // Output offset from the baseBuf + int outOfs; + + for (int i = 0; i < 3; i++) { + for (outOfs = -1; outOfs <= 1; outOfs++) { + + SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); + GCMParameterSpec params = + new GCMParameterSpec(128, new byte[12]); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, key, params); + + // Offset on the particular ByteBuffer (aka position()) + int inOfsInBuf = 1; + int outOfsInBuf = inOfsInBuf + outOfs; + int sliceLen = cipher.getOutputSize(baseBuf.length); + int bufferSize = sliceLen + Math.max(inOfsInBuf, outOfsInBuf); + byte[] buffer; + // Create overlapping input and output buffers + switch (i) { + case 0 -> { + buffer = new byte[bufferSize]; + output = ByteBuffer.wrap(buffer, outOfsInBuf, sliceLen). + slice(); + input = ByteBuffer.wrap(buffer, inOfsInBuf, sliceLen). + slice(); + System.out.println("Using array-backed ByteBuffer"); + in = input.duplicate(); + } + case 1 -> { + buffer = new byte[bufferSize]; + output = ByteBuffer.wrap(buffer, outOfsInBuf, sliceLen). + slice(); + input = ByteBuffer.wrap(buffer, inOfsInBuf, sliceLen). + slice(); + + System.out.println("Using read-only array-backed " + "ByteBuffer"); + in = input.asReadOnlyBuffer(); + } + case 2 -> { + System.out.println("Using direct ByteBuffer"); + ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize); + output = buf.duplicate(); + output.position(outOfsInBuf); + output.limit(sliceLen + outOfsInBuf); + output = output.slice(); + + input = buf.duplicate(); + input.position(inOfsInBuf); + input.limit(sliceLen + inOfsInBuf); + input = input.slice(); + + in = input.duplicate(); + } + default -> { + throw new Exception("Unknown index " + i); + } + } + + // Copy data into shared buffer + input.put(baseBuf); + input.flip(); + in.limit(input.limit()); + + try { + int ctSize = cipher.doFinal(in, output); + + // Get ready to decrypt + byte[] tmp = new byte[ctSize]; + output.flip(); + output.get(tmp); + output.clear(); + + input.clear(); + input.put(tmp); + input.flip(); + + in.clear(); + in.limit(input.limit()); + + cipher.init(Cipher.DECRYPT_MODE, key, params); + cipher.doFinal(in, output); + + output.flip(); + System.out.println("inOfsInBuf = " + inOfsInBuf); + System.out.println("outOfsInBuf = " + outOfsInBuf); + ByteBuffer b = ByteBuffer.wrap(baseBuf); + if (b.compareTo(output) != 0) { + System.err.println( + "\nresult (" + output + "):\n" + + byteToHex(output) + + "\nexpected (" + b + "):\n" + + byteToHex(b)); + throw new Exception("Mismatch"); + } + } catch (Exception e) { + throw new Exception("Error with base offset " + outOfs, e); + } + } + } + } + private static String byteToHex(ByteBuffer bb) { + StringBuilder s = new StringBuilder(); + while (bb.remaining() > 0) { + s.append(String.format("%02x", bb.get())); + } + return s.toString(); + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/SameBuffer.java b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/SameBuffer.java index 30afb8ee8ea67..574e87a579046 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/AEAD/SameBuffer.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/AEAD/SameBuffer.java @@ -29,12 +29,14 @@ import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.GCMParameterSpec; +import jdk.test.lib.Convert; /* * @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 + * @library /test/lib */ public class SameBuffer { @@ -255,6 +257,13 @@ private void runGCMWithSeparateArray(int mode, byte[] AAD, byte[] text, // check if two resutls are equal if (!isEqual(text, myoff, outputText, 0, outputText.length)) { + System.err.println( + "\noutputText: len = " + outputText.length + " txtOffset = " + txtOffset + "\n" + + jdk.test.lib.Convert.byteArrayToHexString(outputText) + "\n" + + "text: len = " + text.length + " myoff = " + myoff + "\n" + + jdk.test.lib.Convert.byteArrayToHexString(text) + "\n" + + "lenght " + lenght); + System.err.println("tlen = " + params.getParameterSpec(GCMParameterSpec.class).getTLen() / 8); throw new RuntimeException("Two results not equal, mode:" + mode); } } @@ -387,6 +396,8 @@ private boolean isEqual(byte[] A, int offsetA, byte[] B, int offsetB, int setB = i + offsetB; if (setA > A.length - 1 || setB > B.length - 1 || A[setA] != B[setB]) { + System.err.println("i = " + i + " A[setA] = " + A[setA] + + " B[setB] = " + B[setB]); return false; } } diff --git a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestKATForGCM.java b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestKATForGCM.java index 3595bbd22f1e0..bdcf465f854eb 100644 --- a/test/jdk/com/sun/crypto/provider/Cipher/AES/TestKATForGCM.java +++ b/test/jdk/com/sun/crypto/provider/Cipher/AES/TestKATForGCM.java @@ -33,6 +33,7 @@ */ +import java.nio.ByteBuffer; import java.security.*; import javax.crypto.*; import javax.crypto.spec.*; @@ -55,6 +56,7 @@ private static byte[] HexToBytes(String hexVal) { } private static class TestVector { + int id; SecretKey key; byte[] plainText; byte[] aad; @@ -63,23 +65,41 @@ private static class TestVector { GCMParameterSpec spec; String info; - TestVector(String key, String iv, String pt, String aad, + TestVector(int id, String key, String iv, String pt, String aad, String ct, String tag) { + this.id = id; this.key = new SecretKeySpec(HexToBytes(key), "AES"); this.plainText = HexToBytes(pt); this.aad = HexToBytes(aad); this.cipherText = HexToBytes(ct); this.tag = HexToBytes(tag); this.spec = new GCMParameterSpec(this.tag.length * 8, HexToBytes(iv)); - this.info = "key=" + key + ", iv=" + iv + ", pt=" + pt + + this.info = "id = " + id + ", key=" + key + ", iv=" + iv + ", pt=" + pt + ",aad=" + aad + ", ct=" + ct + ", tag=" + tag; } + TestVector() {}; + + TestVector duplicate() { + TestVector t = new TestVector(); + t.id = id; + t.key = key; + t.plainText = plainText; + t.cipherText = cipherText; + t.aad = aad; + t.tag = tag; + t.spec = spec; + t.info = info; + return t; + } + public String toString() { return info; } } + static boolean testFailed = false; + // These test vectors are found off NIST's CAVP page // http://csrc.nist.gov/groups/STM/cavp/index.html // inside the link named "GCM Test Vectors", i.e. @@ -88,53 +108,53 @@ public String toString() { private static TestVector[] testValues = { // 96-bit iv w/ 128/120/112/104/96-bit tags // no plain text, no aad - new TestVector("11754cd72aec309bf52f7687212e8957", + new TestVector(1, "11754cd72aec309bf52f7687212e8957", "3c819d9a9bed087615030b65", null, null, null, "250327c674aaf477aef2675748cf6971"), - new TestVector("272f16edb81a7abbea887357a58c1917", + new TestVector(2, "272f16edb81a7abbea887357a58c1917", "794ec588176c703d3d2a7a07", null, null, null, "b6e6f197168f5049aeda32dafbdaeb"), - new TestVector("81b6844aab6a568c4556a2eb7eae752f", + new TestVector(3, "81b6844aab6a568c4556a2eb7eae752f", "ce600f59618315a6829bef4d", null, null, null, "89b43e9dbc1b4f597dbbc7655bb5"), - new TestVector("cde2f9a9b1a004165ef9dc981f18651b", + new TestVector(4, "cde2f9a9b1a004165ef9dc981f18651b", "29512c29566c7322e1e33e8e", null, null, null, "2e58ce7dabd107c82759c66a75"), - new TestVector("b01e45cc3088aaba9fa43d81d481823f", + new TestVector(5, "b01e45cc3088aaba9fa43d81d481823f", "5a2c4a66468713456a4bd5e1", null, null, null, "014280f944f53c681164b2ff"), // 96-bit iv w/ 128/120/112/104/96-bit tags // no plain text, 16-byte aad - new TestVector("77be63708971c4e240d1cb79e8d77feb", + new TestVector(6, "77be63708971c4e240d1cb79e8d77feb", "e0e00f19fed7ba0136a797f3", null, "7a43ec1d9c0a5a78a0b16533a6213cab", null, "209fcc8d3675ed938e9c7166709dd946"), - new TestVector("da0b615656135194ba6d3c851099bc48", + new TestVector(7, "da0b615656135194ba6d3c851099bc48", "d39d4b4d3cc927885090e6c3", null, "e7e5e6f8dac913036cb2ff29e8625e0e", null, "ab967711a5770461724460b07237e2"), - new TestVector("7e0986937a88eef894235aba4a2f43b2", + new TestVector(8, "7e0986937a88eef894235aba4a2f43b2", "92c4a631695907166b422d60", null, "85c185f8518f9f2cd597a8f9208fc76b", null, "3bb916b728df94fe9d1916736be1"), - new TestVector("c3db570d7f0c21e86b028f11465d1dc9", + new TestVector(9, "c3db570d7f0c21e86b028f11465d1dc9", "f86970f58ceef89fc7cb679e", null, "c095240708c0f57c288d86090ae34ee1", null, "e043c52160d652e82c7262fcf4"), - new TestVector("bea48ae4980d27f357611014d4486625", + new TestVector(10, "bea48ae4980d27f357611014d4486625", "32bddb5c3aa998a08556454c", null, "8a50b0b8c7654bced884f7f3afda2ead", @@ -142,56 +162,56 @@ public String toString() { "8e0f6d8bf05ffebe6f500eb1"), // 96-bit iv w/ 128/120/112/104/96-bit tags // no plain text, 20-byte aad - new TestVector("2fb45e5b8f993a2bfebc4b15b533e0b4", + new TestVector(11, "2fb45e5b8f993a2bfebc4b15b533e0b4", "5b05755f984d2b90f94b8027", null, "e85491b2202caf1d7dce03b97e09331c32473941", null, "c75b7832b2a2d9bd827412b6ef5769db"), - new TestVector("9bf406339fcef9675bbcf156aa1a0661", + new TestVector(12, "9bf406339fcef9675bbcf156aa1a0661", "8be4a9543d40f542abacac95", null, "7167cbf56971793186333a6685bbd58d47d379b3", null, "5e7968d7bbd5ba58cfcc750e2ef8f1"), - new TestVector("a2e962fff70fd0f4d63be728b80556fc", + new TestVector(13, "a2e962fff70fd0f4d63be728b80556fc", "1fa7103483de43d09bc23db4", null, "2a58edf1d53f46e4e7ee5e77ee7aeb60fc360658", null, "fa37f2dbbefab1451eae1d0d74ca"), - new TestVector("6bf4fdce82926dcdfc52616ed5f23695", + new TestVector(14, "6bf4fdce82926dcdfc52616ed5f23695", "cc0f5899a10615567e1193ed", null, "3340655592374c1da2f05aac3ee111014986107f", null, "8ad3385cce3b5e7c985908192c"), - new TestVector("4df7a13e43c3d7b66b1a72fac5ba398e", + new TestVector(15, "4df7a13e43c3d7b66b1a72fac5ba398e", "97179a3a2d417908dcf0fb28", null, "cbb7fc0010c255661e23b07dbd804b1e06ae70ac", null, "37791edae6c137ea946cfb40"), // 96-bit iv w/ 128-bit tags, 13/16/32/51-byte plain text, no aad - new TestVector("fe9bb47deb3a61e423c2231841cfd1fb", + new TestVector(16, "fe9bb47deb3a61e423c2231841cfd1fb", "4d328eb776f500a2f7fb47aa", "f1cc3818e421876bb6b8bbd6c9", null, "b88c5c1977b35b517b0aeae967", "43fd4727fe5cdb4b5b42818dea7ef8c9"), - new TestVector("7fddb57453c241d03efbed3ac44e371c", + new TestVector(17, "7fddb57453c241d03efbed3ac44e371c", "ee283a3fc75575e33efd4887", "d5de42b461646c255c87bd2962d3b9a2", null, "2ccda4a5415cb91e135c2a0f78c9b2fd", "b36d1df9b9d5e596f83e8b7f52971cb3"), - new TestVector("9971071059abc009e4f2bd69869db338", + new TestVector(18, "9971071059abc009e4f2bd69869db338", "07a9a95ea3821e9c13c63251", "f54bc3501fed4f6f6dfb5ea80106df0bd836e6826225b75c0222f6e859b35983", null, "0556c159f84ef36cb1602b4526b12009c775611bffb64dc0d9ca9297cd2c6a01", "7870d9117f54811a346970f1de090c41"), - new TestVector("594157ec4693202b030f33798b07176d", + new TestVector(19, "594157ec4693202b030f33798b07176d", "49b12054082660803a1df3df", "3feef98a976a1bd634f364ac428bb59cd51fb159ec1789946918dbd50ea6c9d594a3a31a5269b0da6936c29d063a5fa2cc8a1c", @@ -200,26 +220,26 @@ public String toString() { "c1b7a46a335f23d65b8db4008a49796906e225474f4fe7d39e55bf2efd97fd82d4167de082ae30fa01e465a601235d8d68bc69", "ba92d3661ce8b04687e8788d55417dc2"), // 96-bit iv w/ 128-bit tags, 16-byte plain text, 16/20/48/90-byte aad - new TestVector("c939cc13397c1d37de6ae0e1cb7c423c", + new TestVector(20, "c939cc13397c1d37de6ae0e1cb7c423c", "b3d8cc017cbb89b39e0f67e2", "c3b3c41f113a31b73d9a5cd432103069", "24825602bd12a984e0092d3e448eda5f", "93fe7d9e9bfd10348a5606e5cafa7354", "0032a1dc85f1c9786925a2e71d8272dd"), - new TestVector("d4a22488f8dd1d5c6c19a7d6ca17964c", + new TestVector(21, "d4a22488f8dd1d5c6c19a7d6ca17964c", "f3d5837f22ac1a0425e0d1d5", "7b43016a16896497fb457be6d2a54122", "f1c5d424b83f96c6ad8cb28ca0d20e475e023b5a", "c2bd67eef5e95cac27e3b06e3031d0a8", "f23eacf9d1cdf8737726c58648826e9c"), - new TestVector("89850dd398e1f1e28443a33d40162664", + new TestVector(22, "89850dd398e1f1e28443a33d40162664", "e462c58482fe8264aeeb7231", "2805cdefb3ef6cc35cd1f169f98da81a", "d74e99d1bdaa712864eec422ac507bddbe2b0d4633cd3dff29ce5059b49fe868526c59a2a3a604457bc2afea866e7606", "ba80e244b7fc9025cd031d0f63677e06", "d84a8c3eac57d1bb0e890a8f461d1065"), - new TestVector("bd7c5c63b7542b56a00ebe71336a1588", + new TestVector(23, "bd7c5c63b7542b56a00ebe71336a1588", "87721f23ba9c3c8ea5571abc", "de15ddbb1e202161e8a79af6a55ac6f3", @@ -227,17 +247,17 @@ public String toString() { "41eb28c0fee4d762de972361c863bc80", "9cb567220d0b252eb97bff46e4b00ff8"), // 8/1024-bit iv w/ 128-bit tag, no plain text, no aad - new TestVector("1672c3537afa82004c6b8a46f6f0d026", + new TestVector(24, "1672c3537afa82004c6b8a46f6f0d026", "05", null, null, null, "8e2ad721f9455f74d8b53d3141f27e8e"), - new TestVector("d0f1f4defa1e8c08b4b26d576392027c", + new TestVector(25, "d0f1f4defa1e8c08b4b26d576392027c", "42b4f01eb9f5a1ea5b1eb73b0fb0baed54f387ecaa0393c7d7dffc6af50146ecc021abf7eb9038d4303d91f8d741a11743166c0860208bcc02c6258fd9511a2fa626f96d60b72fcff773af4e88e7a923506e4916ecbd814651e9f445adef4ad6a6b6c7290cc13b956130eef5b837c939fcac0cbbcc9656cd75b13823ee5acdac", null, null, null, "7ab49b57ddf5f62c427950111c5c4f0d"), // 8-bit iv w/ 128-bit tag, 13-byte plain text, 90-byte aad - new TestVector("9f79239f0904eace50784b863e723f6b", + new TestVector(26, "9f79239f0904eace50784b863e723f6b", "d9", "bdb0bb10c87965acd34d146171", @@ -245,7 +265,7 @@ public String toString() { "7e5a7c8dadb3f0c7335b4d9d8d", "6b6ef1f53723a89f3bb7c6d043840717"), // 1024-bit iv w/ 128-bit tag, 51-byte plain text, 48-byte aad - new TestVector("141f1ce91989b07e7eb6ae1dbd81ea5e", + new TestVector(27, "141f1ce91989b07e7eb6ae1dbd81ea5e", "49451da24bd6074509d3cebc2c0394c972e6934b45a1d91f3ce1d3ca69e194aa1958a7c21b6f21d530ce6d2cc5256a3f846b6f9d2f38df0102c4791e57df038f6e69085646007df999751e248e06c47245f4cd3b8004585a7470dee1690e9d2d63169a58d243c0b57b3e5b4a481a3e4e8c60007094ef3adea2e8f05dd3a1396f", @@ -257,56 +277,139 @@ public String toString() { "dbb93bbb56d0439cd09f620a57687f5d"), }; - public boolean execute(TestVector[] testValues) throws Exception { - boolean testFailed = false; + void executeArray(TestVector tv) throws Exception { Cipher c = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); - for (int i = 0; i < testValues.length; i++) { - try { - c.init(Cipher.ENCRYPT_MODE, testValues[i].key, testValues[i].spec); - c.updateAAD(testValues[i].aad); - byte[] ctPlusTag = c.doFinal(testValues[i].plainText); - - c.init(Cipher.DECRYPT_MODE, testValues[i].key, testValues[i].spec); - c.updateAAD(testValues[i].aad); - byte[] pt = c.doFinal(ctPlusTag); // should fail if tag mismatched - - // check encryption/decryption results just to be sure - if (!Arrays.equals(testValues[i].plainText, pt)) { - System.out.println("PlainText diff failed for test# " + i); - testFailed = true; - } - int ctLen = testValues[i].cipherText.length; - if (!Arrays.equals(testValues[i].cipherText, - Arrays.copyOf(ctPlusTag, ctLen))) { - System.out.println("CipherText diff failed for test# " + i); - testFailed = true; - } - int tagLen = testValues[i].tag.length; - if (!Arrays.equals - (testValues[i].tag, - Arrays.copyOfRange(ctPlusTag, ctLen, ctLen+tagLen))) { - System.out.println("Tag diff failed for test# " + i); - testFailed = true; - } - } catch (Exception ex) { - // continue testing other test vectors - System.out.println("Failed Test Vector: " + testValues[i]); - ex.printStackTrace(); + try { + System.out.println("Test #" + tv.id + ": byte[]."); + + c.init(Cipher.ENCRYPT_MODE, tv.key, tv.spec); + c.updateAAD(tv.aad); + byte[] ctPlusTag = c.doFinal(tv.plainText); + + c.init(Cipher.DECRYPT_MODE, tv.key, tv.spec); + c.updateAAD(tv.aad); + byte[] pt = c.doFinal(ctPlusTag); // should fail if tag mismatched + + // check encryption/decryption results just to be sure + if (!Arrays.equals(tv.plainText, pt)) { + System.out.println("PlainText diff failed for test# " + tv.id); testFailed = true; - continue; } + int ctLen = tv.cipherText.length; + if (!Arrays.equals(tv.cipherText, + Arrays.copyOf(ctPlusTag, ctLen))) { + System.out.println("CipherText diff failed for test# " + tv.id); + testFailed = true; + } + int tagLen = tv.tag.length; + if (!Arrays.equals + (tv.tag, + Arrays.copyOfRange(ctPlusTag, ctLen, ctLen+tagLen))) { + System.out.println("Tag diff failed for test# " + tv.id); + testFailed = true; + } + } catch (Exception ex) { + // continue testing other test vectors + System.out.println("Failed Test Vector: " + tv); + ex.printStackTrace(); + testFailed = true; } if (testFailed) { throw new Exception("Test Failed"); } - // passed all tests...hooray! - return true; + } + + void executeByteBuffer(TestVector tv, boolean direct, int offset) throws Exception { + Cipher c = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); + + ByteBuffer src; + ByteBuffer ctdst; + ByteBuffer ptdst; + + if (direct) { + System.out.print("Test #" + tv.id + ": ByteBuffer Direct."); + src = ByteBuffer.allocateDirect(tv.plainText.length + offset); + ctdst = ByteBuffer.allocateDirect(tv.cipherText.length + tv.tag.length + offset); + ptdst = ByteBuffer.allocateDirect(tv.plainText.length + offset); + } else { + System.out.print("Test #" + tv.id + ": ByteBuffer Heap."); + src = ByteBuffer.allocate(tv.plainText.length + offset); + ctdst = ByteBuffer.allocate(tv.cipherText.length + tv.tag.length + offset); + ptdst = ByteBuffer.allocate(tv.plainText.length + offset); + } + + byte[] plainText; + + if (offset > 0) { + System.out.println(" offset = " + offset); + plainText = new byte[tv.plainText.length + offset]; + System.arraycopy(tv.plainText, 0, plainText, offset, + tv.plainText.length); + } else { + System.out.println(); + plainText = tv.plainText; + } + + src.put(plainText); + src.position(offset); + ctdst.position(offset); + ctdst.mark(); + ptdst.position(offset); + ptdst.mark(); + + try { + c.init(Cipher.ENCRYPT_MODE, tv.key, tv.spec); + c.updateAAD(tv.aad); + c.doFinal(src, ctdst); + + ctdst.reset(); + ByteBuffer tag = ctdst.duplicate(); + tag.position(tag.limit() - tv.tag.length); + + c.init(Cipher.DECRYPT_MODE, tv.key, tv.spec); + c.updateAAD(tv.aad); + c.doFinal(ctdst, ptdst); // should fail if tag mismatched + + ptdst.reset(); + // check encryption/decryption results just to be sure + if (ptdst.compareTo(ByteBuffer.wrap(tv.plainText)) != 0) { + System.out.println("\t PlainText diff failed for test# " + tv.id); + testFailed = true; + } + + ctdst.reset(); + ctdst.limit(ctdst.limit() - tv.tag.length); + if (ctdst.compareTo(ByteBuffer.wrap(tv.cipherText)) != 0) { + System.out.println("\t CipherText diff failed for test# " + tv.id); + testFailed = true; + } + + int mismatch = 0; + for (int i = 0; i < tv.tag.length; i++) { + mismatch |= tag.get() ^ tv.tag[i]; + } + if (mismatch != 0) { + System.out.println("\t Tag diff failed for test# " + tv.id); + testFailed = true; + } + } catch (Exception ex) { + // continue testing other test vectors + System.out.println("\t Failed Test Vector ( #" + tv.id + ") : " + tv); + ex.printStackTrace(); + } } public static void main (String[] args) throws Exception { TestKATForGCM test = new TestKATForGCM(); - if (test.execute(testValues)) { - System.out.println("Test Passed!"); + for (TestVector tv : testValues) { + test.executeArray(tv); + test.executeByteBuffer(tv, false, 0); + test.executeByteBuffer(tv, true, 0); + test.executeByteBuffer(tv, false, 2); + test.executeByteBuffer(tv, true, 2); + } + if (!testFailed) { + System.out.println("Tests passed"); } } } diff --git a/test/jdk/com/sun/crypto/provider/Cipher/TextLength/SameBufferOverwrite.java b/test/jdk/com/sun/crypto/provider/Cipher/TextLength/SameBufferOverwrite.java new file mode 100644 index 0000000000000..d110c05a339ef --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/TextLength/SameBufferOverwrite.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020, 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 javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.AlgorithmParameters; +import java.util.Arrays; + + +/* + * @test + * @summary Verify when decrypting over an existing buffer than padding does not + * overwrite past what the plaintext length is. + * + */ + +public class SameBufferOverwrite { + + private SecretKey skey; + private Cipher c; + private int start = 17, end = 17; // default + + SameBufferOverwrite(String algo, String transformation) + throws Exception { + + KeyGenerator kg = KeyGenerator.getInstance(algo, "SunJCE"); + skey = kg.generateKey(); + c = Cipher.getInstance(transformation, "SunJCE"); + } + + /* + * Run the test + */ + void test() throws Exception { + byte[] in = new byte[end + (c.getBlockSize() - (end % c.getBlockSize()))]; + Arrays.fill(in, (byte)8); + int len = start; + AlgorithmParameters params = null; + + System.out.println("Testing transformation: " + c.getAlgorithm() + + ", byte length from " + start + " to " + end); + while (end >= len) { + // encrypt + c.init(Cipher.ENCRYPT_MODE, skey, params); + byte[] out = c.doFinal(in, 0, len); + System.out.println(" enc = " + byteToHex(out)); + System.out.println(" => enc " + len + " bytes, ret " + + (out == null ? "null" : (out.length + " byte"))); + + // decrypt + params = c.getParameters(); + c.init(Cipher.DECRYPT_MODE, skey, params); + int rLen = c.doFinal(out, 0, out.length, in); + System.out.println(" dec = " + byteToHex(in)); + System.out.println(" => dec " + out.length + " bytes, ret " + + rLen + " byte"); + // check if more than rLen bytes are written into 'in' + for (int j = rLen; j < in.length; j++) { + if (in[j] != (byte) 8) { + throw new Exception("Value check failed at index " + j); + } + } + System.out.println(" Test Passed: len = " + len); + len++; + + // Because GCM doesn't allow params reuse + if (c.getAlgorithm().contains("GCM")) { + params = null; + } + } + } + + /** + * Builder method for the test to run data lengths from the start value to + * the end value. To do one length, have start and end equal that number. + * @param start starting data length + * @param end ending data length + */ + SameBufferOverwrite iterate(int start, int end) { + this.start = start; + this.end = end; + return this; + } + + public static void main(String args[]) throws Exception { + new SameBufferOverwrite("AES", "AES/GCM/NoPadding").iterate(1, 25). + test(); + new SameBufferOverwrite("AES", "AES/CTR/NoPadding").iterate(1, 25). + test(); + new SameBufferOverwrite("AES", "AES/CBC/PKCS5Padding").iterate(1, 25). + test(); + new SameBufferOverwrite("AES", "AES/ECB/PKCS5Padding").iterate(1, 25). + test(); + new SameBufferOverwrite("DES", "DES/CBC/PKCS5Padding").iterate(1, 17). + test(); + new SameBufferOverwrite("DESede", "DESede/CBC/PKCS5Padding").iterate(1, 17). + test(); + } + + private static String byteToHex(byte[] barray) { + StringBuilder s = new StringBuilder(); + for (byte b : barray) { + s.append(String.format("%02x", b)); + } + return s.toString(); + } +} diff --git a/test/jdk/javax/crypto/CipherSpi/CipherByteBufferOverwriteTest.java b/test/jdk/javax/crypto/CipherSpi/CipherByteBufferOverwriteTest.java index 2fdd4f05a6973..260d667714744 100644 --- a/test/jdk/javax/crypto/CipherSpi/CipherByteBufferOverwriteTest.java +++ b/test/jdk/javax/crypto/CipherSpi/CipherByteBufferOverwriteTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 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 @@ -26,15 +26,21 @@ * @bug 8181386 * @summary CipherSpi ByteBuffer to byte array conversion fails for * certain data overlap conditions - * @run main CipherByteBufferOverwriteTest 0 false - * @run main CipherByteBufferOverwriteTest 0 true - * @run main CipherByteBufferOverwriteTest 4 false - * @run main CipherByteBufferOverwriteTest 4 true + * @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 0 false + * @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 0 true + * @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 4 false + * @run main CipherByteBufferOverwriteTest AES/CBC/PKCS5Padding 4 true + * @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 0 false + * @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 0 true + * @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 4 false + * @run main CipherByteBufferOverwriteTest AES/GCM/NoPadding 4 true */ +import java.math.BigInteger; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; @@ -44,7 +50,7 @@ public class CipherByteBufferOverwriteTest { private static final boolean DEBUG = false; - private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding"; + private static String transformation; // must be larger than the temp array size, i.e. 4096, hardcoded in // javax.crypto.CipherSpi class @@ -53,8 +59,7 @@ public class CipherByteBufferOverwriteTest { private static final int CIPHERTEXT_BUFFER_SIZE = PLAINTEXT_SIZE + 32; private static final SecretKey KEY = new SecretKeySpec(new byte[16], "AES"); - private static final AlgorithmParameterSpec PARAMS = - new IvParameterSpec(new byte[16]); + private static AlgorithmParameterSpec params; private static ByteBuffer inBuf; private static ByteBuffer outBuf; @@ -65,9 +70,15 @@ private enum BufferType { public static void main(String[] args) throws Exception { - int offset = Integer.parseInt(args[0]); - boolean useRO = Boolean.parseBoolean(args[1]); + transformation = args[0]; + int offset = Integer.parseInt(args[1]); + boolean useRO = Boolean.parseBoolean(args[2]); + if (transformation.equalsIgnoreCase("AES/GCM/NoPadding")) { + params = new GCMParameterSpec(16 * 8, new byte[16]); + } else { + params = new IvParameterSpec(new byte[16]); + } // an all-zeros plaintext is the easiest way to demonstrate the issue, // but it fails with any plaintext, of course byte[] expectedPT = new byte[PLAINTEXT_SIZE]; @@ -75,8 +86,8 @@ public static void main(String[] args) throws Exception { System.arraycopy(expectedPT, 0, buf, 0, PLAINTEXT_SIZE); // generate expected cipher text using byte[] methods - Cipher c = Cipher.getInstance(TRANSFORMATION); - c.init(Cipher.ENCRYPT_MODE, KEY, PARAMS); + Cipher c = Cipher.getInstance(transformation); + c.init(Cipher.ENCRYPT_MODE, KEY, params); byte[] expectedCT = c.doFinal(expectedPT); // Test#1: against ByteBuffer generated with allocate(int) call @@ -89,9 +100,9 @@ public static void main(String[] args) throws Exception { // Test#2: against direct ByteBuffer prepareBuffers(BufferType.DIRECT, useRO, buf.length, buf, 0, PLAINTEXT_SIZE, offset); - System.out.println("\tDIRECT: passed"); runTest(offset, expectedPT, expectedCT); + System.out.println("\tDIRECT: passed"); // Test#3: against ByteBuffer wrapping existing array prepareBuffers(BufferType.WRAP, useRO, buf.length, @@ -150,8 +161,8 @@ private static void prepareBuffers(BufferType type, private static void runTest(int ofs, byte[] expectedPT, byte[] expectedCT) throws Exception { - Cipher c = Cipher.getInstance(TRANSFORMATION); - c.init(Cipher.ENCRYPT_MODE, KEY, PARAMS); + Cipher c = Cipher.getInstance(transformation); + c.init(Cipher.ENCRYPT_MODE, KEY, params); int ciphertextSize = c.doFinal(inBuf, outBuf); // read out the encrypted result @@ -166,14 +177,20 @@ private static void runTest(int ofs, byte[] expectedPT, byte[] expectedCT) outBuf.get(finalCT); if (!Arrays.equals(finalCT, expectedCT)) { + System.err.println("Ciphertext mismatch:" + + "\nresult (len=" + finalCT.length + "):\n" + + String.format("%0" + (finalCT.length << 1) + "x", + new BigInteger(1, finalCT)) + + "\nexpected (len=" + expectedCT.length + "):\n" + + String.format("%0" + (expectedCT.length << 1) + "x", + new BigInteger(1, expectedCT))); throw new Exception("ERROR: Ciphertext does not match"); } // now do decryption outBuf.position(ofs); outBuf.limit(ofs + ciphertextSize); - - c.init(Cipher.DECRYPT_MODE, KEY, PARAMS); + c.init(Cipher.DECRYPT_MODE, KEY, params); ByteBuffer finalPTBuf = ByteBuffer.allocate( c.getOutputSize(outBuf.remaining())); c.doFinal(outBuf, finalPTBuf); @@ -184,6 +201,13 @@ private static void runTest(int ofs, byte[] expectedPT, byte[] expectedCT) finalPTBuf.get(finalPT); if (!Arrays.equals(finalPT, expectedPT)) { + System.err.println("Ciphertext mismatch " + + "):\nresult (len=" + finalCT.length + "):\n" + + String.format("%0" + (finalCT.length << 1) + "x", + new BigInteger(1, finalCT)) + + "\nexpected (len=" + expectedCT.length + "):\n" + + String.format("%0" + (expectedCT.length << 1) + "x", + new BigInteger(1, expectedCT))); throw new Exception("ERROR: Plaintext does not match"); } } diff --git a/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java b/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java index b0d15afc53627..98c7afb2738f7 100644 --- a/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java +++ b/test/jdk/javax/net/ssl/SSLSession/CheckSessionContext.java @@ -31,11 +31,21 @@ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false CheckSessionContext * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext + * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true CheckSessionContext * */ +import javax.net.ssl.SSLSession; + public class CheckSessionContext { + static void toHex(byte[] id) { + for (byte b : id) { + System.out.printf("%02X ", b); + } + System.out.println(); + } + public static void main(String[] args) throws Exception { TLSBase.Server server = new TLSBase.Server(); @@ -46,6 +56,17 @@ public static void main(String[] args) throws Exception { } else { System.out.println("Context was found"); } + SSLSession ss = server.getSession(client1); + System.out.println(ss); + byte[] id = ss.getId(); + System.out.print("id = "); + toHex(id); + System.out.println("ss.getSessionContext().getSession(id) = " + ss.getSessionContext().getSession(id)); + if (ss.getSessionContext().getSession(id) != null) { + id = ss.getSessionContext().getSession(id).getId(); + System.out.print("id = "); + toHex(id); + } server.close(client1); client1.close();