Backport of JDK-8178374 to jdk8u

Jonathan Joo jonathanjoo at google.com
Fri May 29 23:11:17 UTC 2020


I'm seeing that prior to sending my email (in plain text mode), the
whitespace looks OK, but once I send it there seems to be problems....

On Fri, May 29, 2020 at 7:09 PM Jonathan Joo <jonathanjoo at google.com> wrote:
>
> Hmm, not sure why that happened. Does this work? (On my end the
> whitespace seems to be fine)
>
> -------
>
> diff --git a/src/share/classes/javax/crypto/CipherSpi.java
> b/src/share/classes/javax/crypto/CipherSpi.java
> --- a/src/share/classes/javax/crypto/CipherSpi.java
> +++ b/src/share/classes/javax/crypto/CipherSpi.java
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
> + * Copyright (c) 1997, 2018, 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
> @@ -755,6 +755,7 @@ public abstract class CipherSpi {
>              return 0;
>          }
>          int outLenNeeded = engineGetOutputSize(inLen);
> +
>          if (output.remaining() < outLenNeeded) {
>              throw new ShortBufferException("Need at least " + outLenNeeded
>                  + " bytes of space in output buffer");
> @@ -762,98 +763,77 @@ public abstract class CipherSpi {
>
>          boolean a1 = input.hasArray();
>          boolean a2 = output.hasArray();
> -
> -        if (a1 && a2) {
> -            byte[] inArray = input.array();
> -            int inOfs = input.arrayOffset() + inPos;
> -            byte[] outArray = output.array();
> +        int total = 0;
> +        byte[] inArray, outArray;
> +        if (a2) { // output has an accessible byte[]
> +            outArray = output.array();
>              int outPos = output.position();
>              int outOfs = output.arrayOffset() + outPos;
> -            int n;
> -            if (isUpdate) {
> -                n = engineUpdate(inArray, inOfs, inLen, outArray, outOfs);
> -            } else {
> -                n = engineDoFinal(inArray, inOfs, inLen, outArray, outOfs);
> -            }
> -            input.position(inLimit);
> -            output.position(outPos + n);
> -            return n;
> -        } else if (!a1 && a2) {
> -            int outPos = output.position();
> -            byte[] outArray = output.array();
> -            int outOfs = output.arrayOffset() + outPos;
> -            byte[] inArray = new byte[getTempArraySize(inLen)];
> -            int total = 0;
> -            do {
> -                int chunk = Math.min(inLen, inArray.length);
> -                if (chunk > 0) {
> -                    input.get(inArray, 0, chunk);
> +
> +            if (a1) { // input also has an accessible byte[]
> +                inArray = input.array();
> +                int inOfs = input.arrayOffset() + inPos;
> +                if (isUpdate) {
> +                    total = engineUpdate(inArray, inOfs, inLen,
> outArray, outOfs);
> +                } else {
> +                    total = engineDoFinal(inArray, inOfs, inLen,
> outArray, outOfs);
>                  }
> -                int n;
> -                if (isUpdate || (inLen != chunk)) {
> -                    n = engineUpdate(inArray, 0, chunk, outArray, outOfs);
> -                } else {
> -                    n = engineDoFinal(inArray, 0, chunk, outArray, outOfs);
> -                }
> -                total += n;
> -                outOfs += n;
> -                inLen -= chunk;
> -            } while (inLen > 0);
> -            output.position(outPos + total);
> -            return total;
> -        } else { // output is not backed by an accessible byte[]
> -            byte[] inArray;
> -            int inOfs;
> -            if (a1) {
> -                inArray = input.array();
> -                inOfs = input.arrayOffset() + inPos;
> -            } else {
> +                input.position(inLimit);
> +            } else { // input does not have accessible byte[]
>                  inArray = new byte[getTempArraySize(inLen)];
> -                inOfs = 0;
> +                do {
> +                    int chunk = Math.min(inLen, inArray.length);
> +                    if (chunk > 0) {
> +                        input.get(inArray, 0, chunk);
> +                    }
> +                    int n;
> +                    if (isUpdate || (inLen > chunk)) {
> +                        n = engineUpdate(inArray, 0, chunk, outArray, outOfs);
> +                    } else {
> +                        n = engineDoFinal(inArray, 0, chunk, outArray, outOfs);
> +                    }
> +                    total += n;
> +                    outOfs += n;
> +                    inLen -= chunk;
> +                } while (inLen > 0);
>              }
> -            byte[] outArray = new byte[getTempArraySize(outLenNeeded)];
> -            int outSize = outArray.length;
> -            int total = 0;
> -            boolean resized = false;
> -            do {
> -                int chunk =
> -                    Math.min(inLen, (outSize == 0? inArray.length : outSize));
> -                if (!a1 && !resized && chunk > 0) {
> -                    input.get(inArray, 0, chunk);
> -                    inOfs = 0;
> +            output.position(outPos + total);
> +        } else { // output does not have an accessible byte[]
> +            if (a1) { // but input has an accessible byte[]
> +                inArray = input.array();
> +                int inOfs = input.arrayOffset() + inPos;
> +                if (isUpdate) {
> +                    outArray = engineUpdate(inArray, inOfs, inLen);
> +                } else {
> +                    outArray = engineDoFinal(inArray, inOfs, inLen);
> +                }
> +                input.position(inLimit);
> +                if (outArray != null && outArray.length != 0) {
> +                    output.put(outArray);
> +                    total = outArray.length;
>                  }
> -                try {
> +            } else { // input also does not have an accessible byte[]
> +                inArray = new byte[getTempArraySize(inLen)];
> +                do {
> +                    int chunk = Math.min(inLen, inArray.length);
> +                    if (chunk > 0) {
> +                        input.get(inArray, 0, chunk);
> +                    }
>                      int n;
> -                    if (isUpdate || (inLen != chunk)) {
> -                        n = engineUpdate(inArray, inOfs, chunk, outArray, 0);
> +                    if (isUpdate || (inLen > chunk)) {
> +                        outArray = engineUpdate(inArray, 0, chunk);
>                      } else {
> -                        n = engineDoFinal(inArray, inOfs, chunk, outArray, 0);
> +                        outArray = engineDoFinal(inArray, 0, chunk);
>                      }
> -                    resized = false;
> -                    inOfs += chunk;
> +                    if (outArray != null && outArray.length != 0) {
> +                        output.put(outArray);
> +                        total += outArray.length;
> +                    }
>                      inLen -= chunk;
> -                    if (n > 0) {
> -                        output.put(outArray, 0, n);
> -                        total += n;
> -                    }
> -                } catch (ShortBufferException e) {
> -                    if (resized) {
> -                        // we just resized the output buffer, but it still
> -                        // did not work. Bug in the provider, abort
> -                        throw (ProviderException)new ProviderException
> -                            ("Could not determine buffer size").initCause(e);
> -                    }
> -                    // output buffer is too small, realloc and try again
> -                    resized = true;
> -                    outSize = engineGetOutputSize(chunk);
> -                    outArray = new byte[outSize];
> -                }
> -            } while (inLen > 0);
> -            if (a1) {
> -                input.position(inLimit);
> +                } while (inLen > 0);
>              }
> -            return total;
>          }
> +        return total;
>      }
>
>      /**
> diff --git a/test/javax/crypto/CipherSpi/TestGCMWithByteBuffer.java
> b/test/javax/crypto/CipherSpi/TestGCMWithByteBuffer.java
> new file mode 100644
> --- /dev/null
> +++ b/test/javax/crypto/CipherSpi/TestGCMWithByteBuffer.java
> @@ -0,0 +1,165 @@
> +/*
> + * Copyright (c) 2018, 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
> + * @bug 8178374
> + * @summary Test GCM decryption with various types of input/output
> + *     ByteBuffer objects
> + * @key randomness
> + */
> +
> +import java.nio.ByteBuffer;
> +import java.security.*;
> +import java.util.Random;
> +
> +import javax.crypto.Cipher;
> +import javax.crypto.SecretKey;
> +import javax.crypto.AEADBadTagException;
> +import javax.crypto.spec.*;
> +
> +public class TestGCMWithByteBuffer {
> +
> +    private static Random random = new SecureRandom();
> +    private static int dataSize = 4096; // see javax.crypto.CipherSpi
> +    private static int multiples = 3;
> +
> +    public static void main(String args[]) throws Exception {
> +        Provider[] provs = Security.getProviders();
> +        for (Provider p : provs) {
> +            try {
> +                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", p);
> +                test(cipher);
> +            } catch (NoSuchAlgorithmException nsae) {
> +                // skip testing due to no support
> +                continue;
> +            }
> +        }
> +    }
> +
> +    private static void test(Cipher cipher) throws Exception {
> +        System.out.println("Testing " + cipher.getProvider());
> +
> +        boolean failedOnce = false;
> +        Exception failedReason = null;
> +
> +        int tagLen = 96; // in bits
> +        byte[] keyBytes = new byte[16];
> +        random.nextBytes(keyBytes);
> +        byte[] dataChunk = new byte[dataSize];
> +        random.nextBytes(dataChunk);
> +
> +        SecretKey key = new SecretKeySpec(keyBytes, "AES");
> +        // re-use key bytes as IV as the real test is buffer calculation
> +        GCMParameterSpec s = new GCMParameterSpec(tagLen, keyBytes);
> +
> +        /*
> +         * Iterate through various sizes to make sure that the code works with
> +         * internal temp buffer size 4096.
> +         */
> +        for (int t = 1; t <= multiples; t++) {
> +            int size = t * dataSize;
> +
> +            System.out.println("\nTesting data size: " + size);
> +
> +            try {
> +                decrypt(cipher, key, s, dataChunk, t,
> +                        ByteBuffer.allocate(dataSize),
> +                        ByteBuffer.allocate(size),
> +                        ByteBuffer.allocateDirect(dataSize),
> +                        ByteBuffer.allocateDirect(size));
> +            } catch (Exception e) {
> +                System.out.println("\tFailed with data size " + size);
> +                failedOnce = true;
> +                failedReason = e;
> +            }
> +        }
> +        if (failedOnce) {
> +            throw failedReason;
> +        }
> +        System.out.println("\n=> Passed...");
> +    }
> +
> +    private enum TestVariant {
> +        HEAP_HEAP, HEAP_DIRECT, DIRECT_HEAP, DIRECT_DIRECT
> +    };
> +
> +    private static void decrypt(Cipher cipher, SecretKey key,
> +            GCMParameterSpec s, byte[] dataChunk, int multiples,
> +            ByteBuffer heapIn, ByteBuffer heapOut, ByteBuffer directIn,
> +            ByteBuffer directOut) throws Exception {
> +
> +        ByteBuffer inBB = null;
> +        ByteBuffer outBB = null;
> +
> +        // try various combinations of input/output
> +        for (TestVariant tv : TestVariant.values()) {
> +            System.out.println(" " + tv);
> +
> +            switch (tv) {
> +            case HEAP_HEAP:
> +                inBB = heapIn;
> +                outBB = heapOut;
> +                break;
> +            case HEAP_DIRECT:
> +                inBB = heapIn;
> +                outBB = directOut;
> +                break;
> +            case DIRECT_HEAP:
> +                inBB = directIn;
> +                outBB = heapOut;
> +                break;
> +            case DIRECT_DIRECT:
> +                inBB = directIn;
> +                outBB = directOut;
> +                break;
> +            }
> +
> +            // prepare input and output buffers
> +            inBB.clear();
> +            inBB.put(dataChunk);
> +
> +            outBB.clear();
> +
> +            try {
> +                // Always re-init the Cipher object so cipher is in
> +                // a good state for future testing
> +                cipher.init(Cipher.DECRYPT_MODE, key, s);
> +
> +                for (int i = 0; i < multiples; i++) {
> +                    inBB.flip();
> +                    cipher.update(inBB, outBB);
> +                    if (inBB.hasRemaining()) {
> +                        throw new Exception("buffer not empty");
> +                    }
> +                }
> +                // finish decryption and process all data buffered
> +                cipher.doFinal(inBB, outBB);
> +                throw new RuntimeException("Error: doFinal completed
> without exception");
> +            } catch (AEADBadTagException ex) {
> +                System.out.println("Expected AEADBadTagException thrown");
> +                continue;
> +            }
> +        }
> +    }
> +}
>
> On Fri, May 29, 2020 at 7:03 PM Martin Buchholz <martinrb at google.com> wrote:
> >
> > I'll help my colleague Jonathan with this change.
> >
> > Jonathan, I had trouble applying this patch because line breaks were
> > "helpfully" inserted e.g.
> >
> > +                    total = engineUpdate(inArray, inOfs, inLen,
> > outArray, outOfs);
> >
> > Not sure which bit of software to blame.
> > If you copy-paste ordinary text into a Plain text mode message, that
> > ought to preserve the whitespace.


More information about the jdk8u-dev mailing list