All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conscrypt.OpenSSLAeadCipher Maven / Gradle / Ivy

There is a newer version: 2.5.2
Show newest version
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conscrypt;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;

@Internal
public abstract class OpenSSLAeadCipher extends OpenSSLCipher {
    /**
     * The default tag size when one is not specified. Default to
     * full-length tags (128-bits or 16 octets).
     */
    static final int DEFAULT_TAG_SIZE_BITS = 16 * 8;

    /**
     * Keeps track of the last used block size.
     */
    private static int lastGlobalMessageSize = 32;

    /**
     * The previously used key to prevent key + nonce (IV) reuse.
     */
    private byte[] previousKey;

    /**
     * The previously used nonce (IV) to prevent key + nonce reuse.
     */
    private byte[] previousIv;

    /**
     * When set this instance must be initialized before use again. This prevents key
     * and IV reuse.
     */
    private boolean mustInitialize;

    /**
     * The byte array containing the bytes written.
     */
    byte[] buf;

    /**
     * The number of bytes written.
     */
    int bufCount;

    /**
     * AEAD cipher reference.
     */
    long evpAead;

    /**
     * Additional authenticated data.
     */
    private byte[] aad;

    /**
     * The length of the AEAD cipher tag in bytes.
     */
    int tagLengthInBytes;

    public OpenSSLAeadCipher(Mode mode) {
        super(mode, Padding.NOPADDING);
    }

    private void checkInitialization() {
        if (mustInitialize) {
            throw new IllegalStateException(
                    "Cannot re-use same key and IV for multiple encryptions");
        }
    }

    /** Constant-time array comparison.  Since we are using this to compare keys, we want to
     * ensure there's no opportunity for a timing attack. */
    private boolean arraysAreEqual(byte[] a, byte[] b) {
        if (a.length != b.length) {
            return false;
        }

        int diff = 0;
        for (int i = 0; i < a.length; i++) {
            diff |= a[i] ^ b[i];
        }
        return diff == 0;
    }

    private void expand(int i) {
        /* Can the buffer handle i more bytes, if not expand it */
        if (bufCount + i <= buf.length) {
            return;
        }

        byte[] newbuf = new byte[(bufCount + i) * 2];
        System.arraycopy(buf, 0, newbuf, 0, bufCount);
        buf = newbuf;
    }

    private void reset() {
        aad = null;
        final int lastBufSize = lastGlobalMessageSize;
        if (buf == null) {
            buf = new byte[lastBufSize];
        } else if (bufCount > 0 && bufCount != lastBufSize) {
            lastGlobalMessageSize = bufCount;
            if (buf.length != bufCount) {
                buf = new byte[bufCount];
            }
        }
        bufCount = 0;
    }

    @Override
    void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params,
            SecureRandom random) throws InvalidKeyException,
        InvalidAlgorithmParameterException {
        byte[] iv;
        final int tagLenBits;
        if (params == null) {
            iv = null;
            tagLenBits = DEFAULT_TAG_SIZE_BITS;
        } else {
            GCMParameters gcmParams = Platform.fromGCMParameterSpec(params);
            if (gcmParams != null) {
                iv = gcmParams.getIV();
                tagLenBits = gcmParams.getTLen();
            } else if (params instanceof IvParameterSpec) {
                IvParameterSpec ivParams = (IvParameterSpec) params;
                iv = ivParams.getIV();
                tagLenBits = DEFAULT_TAG_SIZE_BITS;
            } else {
                iv = null;
                tagLenBits = DEFAULT_TAG_SIZE_BITS;
            }
        }

        checkSupportedTagLength(tagLenBits);

        tagLengthInBytes = tagLenBits / 8;

        final boolean encrypting = isEncrypting();

        evpAead = getEVP_AEAD(encodedKey.length);

        final int expectedIvLength = NativeCrypto.EVP_AEAD_nonce_length(evpAead);
        if (iv == null && expectedIvLength != 0) {
            if (!encrypting) {
                throw new InvalidAlgorithmParameterException("IV must be specified in " + mode
                        + " mode");
            }

            iv = new byte[expectedIvLength];
            if (random != null) {
                random.nextBytes(iv);
            } else {
                NativeCrypto.RAND_bytes(iv);
            }
        } else if (expectedIvLength == 0 && iv != null) {
            throw new InvalidAlgorithmParameterException("IV not used in " + mode + " mode");
        } else if (iv != null && iv.length != expectedIvLength) {
            throw new InvalidAlgorithmParameterException("Expected IV length of "
                    + expectedIvLength + " but was " + iv.length);
        }

        if (isEncrypting() && iv != null && !allowsNonceReuse()) {
            if (previousKey != null && previousIv != null
                    && arraysAreEqual(previousKey, encodedKey)
                    && arraysAreEqual(previousIv, iv)) {
                mustInitialize = true;
                throw new InvalidAlgorithmParameterException(
                        "When using AEAD key and IV must not be re-used");
            }

            this.previousKey = encodedKey;
            this.previousIv = iv;
        }
        mustInitialize = false;
        this.iv = iv;
        reset();
    }

    void checkSupportedTagLength(int tagLenBits)
            throws InvalidAlgorithmParameterException {
        if (tagLenBits % 8 != 0) {
            throw new InvalidAlgorithmParameterException(
                    "Tag length must be a multiple of 8; was " + tagLenBits);
        }
    }

    /**
     * Returns whether reusing nonces is allowed (aka, whether this is nonce misuse-resistant).
     * Most AEAD ciphers are not, but some are specially constructed so that reusing a key/nonce
     * pair is safe.
     */
    boolean allowsNonceReuse() {
        return false;
    }

    @Override
    protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
            int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
        BadPaddingException {
        // Because the EVP_AEAD updateInternal processes input but doesn't create any output
        // (and thus can't check the output buffer), we need to add this check before the
        // superclass' processing to ensure that updateInternal is never called if the
        // output buffer isn't large enough.
        if (output != null) {
            if (getOutputSizeForFinal(inputLen) > output.length - outputOffset) {
                throw new ShortBufferException("Insufficient output space");
            }
        }
        return super.engineDoFinal(input, inputOffset, inputLen, output, outputOffset);
    }

    @Override
    int updateInternal(byte[] input, int inputOffset, int inputLen, byte[] output,
            int outputOffset, int maximumLen) throws ShortBufferException {
        checkInitialization();
        if (buf == null) {
            throw new IllegalStateException("Cipher not initialized");
        }

        ArrayUtils.checkOffsetAndCount(input.length, inputOffset, inputLen);
        if (inputLen > 0) {
            expand(inputLen);
            System.arraycopy(input, inputOffset, buf, this.bufCount, inputLen);
            this.bufCount += inputLen;
        }
        return 0;
    }

    @SuppressWarnings("LiteralClassName")
    private void throwAEADBadTagExceptionIfAvailable(String message, Throwable cause)
            throws BadPaddingException {
        Constructor aeadBadTagConstructor;
        try {
            aeadBadTagConstructor = Class.forName("javax.crypto.AEADBadTagException")
                                            .getConstructor(String.class);
        } catch (Exception ignored) {
            return;
        }

        BadPaddingException badTagException = null;
        try {
            badTagException = (BadPaddingException) aeadBadTagConstructor.newInstance(message);
            badTagException.initCause(cause);
        } catch (IllegalAccessException e2) {
            // Fall through
        } catch (InstantiationException e2) {
            // Fall through
        } catch (InvocationTargetException e2) {
            throw(BadPaddingException) new BadPaddingException().initCause(
                    e2.getTargetException());
        }
        if (badTagException != null) {
            throw badTagException;
        }
    }

    @Override
    int doFinalInternal(byte[] output, int outputOffset, int maximumLen)
            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
        checkInitialization();
        final int bytesWritten;
        try {
            if (isEncrypting()) {
                bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal(evpAead, encodedKey,
                        tagLengthInBytes, output, outputOffset, iv, buf, 0, bufCount, aad);
            } else {
                bytesWritten = NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey,
                        tagLengthInBytes, output, outputOffset, iv, buf, 0, bufCount, aad);
            }
        } catch (BadPaddingException e) {
            throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause());
            throw e;
        }
        if (isEncrypting()) {
            mustInitialize = true;
        }
        reset();
        return bytesWritten;
    }

    @Override
    void checkSupportedPadding(Padding padding) throws NoSuchPaddingException {
        if (padding != Padding.NOPADDING) {
            throw new NoSuchPaddingException("Must be NoPadding for AEAD ciphers");
        }
    }

    /**
     * AEAD buffers everything until a final output.
     */
    @Override
    int getOutputSizeForUpdate(int inputLen) {
        return 0;
    }

    @Override
    int getOutputSizeForFinal(int inputLen) {
        return bufCount + inputLen
                + (isEncrypting() ? NativeCrypto.EVP_AEAD_max_overhead(evpAead) : 0);
    }

    // Intentionally missing Override to compile on old versions of Android
    @SuppressWarnings("MissingOverride")
    protected void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
        checkInitialization();
        if (aad == null) {
            aad = Arrays.copyOfRange(input, inputOffset, inputOffset + inputLen);
        } else {
            int newSize = aad.length + inputLen;
            byte[] newaad = new byte[newSize];
            System.arraycopy(aad, 0, newaad, 0, aad.length);
            System.arraycopy(input, inputOffset, newaad, aad.length, inputLen);
            aad = newaad;
        }
    }

    // Intentionally missing Override to compile on old versions of Android
    @SuppressWarnings("MissingOverride")
    protected void engineUpdateAAD(ByteBuffer buf) {
        checkInitialization();
        if (aad == null) {
            aad = new byte[buf.remaining()];
            buf.get(aad);
        } else {
            int newSize = aad.length + buf.remaining();
            byte[] newaad = new byte[newSize];
            System.arraycopy(aad, 0, newaad, 0, aad.length);
            buf.get(newaad, aad.length, buf.remaining());
            aad = newaad;
        }
    }

    abstract long getEVP_AEAD(int keyLength) throws InvalidKeyException;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy