OpenSslCipher.java

 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.commons.crypto.cipher;

import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Objects;
import java.util.Properties;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;

/**
 * Implements the CryptoCipher using JNI into OpenSSL.
 * <p>
 * this class is not public/protected so does not appear in the main Javadoc Please ensure that property use is documented in the enum
 * CryptoRandomFactory.RandomProvider
 * </p>
 */
final class OpenSslCipher implements CryptoCipher {

    private final OpenSsl openSslEngine;
    private boolean initialized;

    private final String transformation;

    /**
     * Constructs a {@link CryptoCipher} using JNI into OpenSSL
     *
     * @param props  properties for OpenSSL openSslEngine (unused)
     * @param transformation  transformation for OpenSSL openSslEngine (algorithm/mode/padding)
     * @throws GeneralSecurityException if OpenSSL openSslEngine initialize failed
     */
    public OpenSslCipher(final Properties props, final String transformation) // NOPMD
            throws GeneralSecurityException {
        this.transformation = transformation;

        final Throwable loadingFailureReason = OpenSsl.getLoadingFailureReason();
        if (loadingFailureReason != null) {
            throw new IllegalStateException(loadingFailureReason);
        }

        openSslEngine = OpenSsl.getInstance(transformation);
    }

    /**
     * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context.
     */
    @Override
    public void close() {
        openSslEngine.clean();
    }

    /**
     * Encrypts or decrypts data in a single-part operation, or finishes a
     * multiple-part operation.
     *
     * @param input the input byte array
     * @param inputOffset the offset in input where the input starts
     * @param inputLen the input length
     * @param output the byte array for the result
     * @param outputOffset the offset in output where the result is stored
     * @return the number of bytes stored in output
     * @throws ShortBufferException if the given output byte array is too small
     *         to hold the result
     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
     *         (un)padding has been requested, but the decrypted data is not
     *         bounded by the appropriate padding bytes
     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
     *         padding has been requested (only in encryption mode), and the
     *         total input length of the data processed by this openSslEngine is not a
     *         multiple of block size; or if this encryption algorithm is unable
     *         to process the input data provided.
     */
    @Override
    public int doFinal(final byte[] input, final int inputOffset, final int inputLen,
            final byte[] output, final int outputOffset) throws ShortBufferException,
            IllegalBlockSizeException, BadPaddingException {
        return openSslEngine.doFinal(input, inputOffset, inputLen, output,outputOffset);
    }

    /**
     * Encrypts or decrypts data in a single-part operation, or finishes a
     * multiple-part operation. The data is encrypted or decrypted, depending on
     * how this openSslEngine was initialized.
     *
     * @param inBuffer the input ByteBuffer
     * @param outBuffer the output ByteBuffer
     * @return int number of bytes stored in {@code output}
     * @throws BadPaddingException if this openSslEngine is in decryption mode, and
     *         (un)padding has been requested, but the decrypted data is not
     *         bounded by the appropriate padding bytes
     * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
     *         padding has been requested (only in encryption mode), and the
     *         total input length of the data processed by this openSslEngine is not a
     *         multiple of block size; or if this encryption algorithm is unable
     *         to process the input data provided.
     * @throws ShortBufferException if the given output buffer is too small to
     *         hold the result
     */
    @Override
    public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
            throws ShortBufferException, IllegalBlockSizeException,
            BadPaddingException {
        return openSslEngine.doFinal(inBuffer, outBuffer);
    }

    /**
     * Returns the algorithm name of this {@code CryptoCipher} object.
     *
     * <p>
     * This is the same name that was specified in one of the
     * {@code CryptoCipherFactory#getInstance} calls that created this
     * {@code CryptoCipher} object..
     * </p>
     *
     * @return the algorithm name of this {@code CryptoCipher} object.
     */
    @Override
    public String getAlgorithm() {
        return transformation;
    }

    /**
     * Returns the block size (in bytes).
     *
     * @return the block size (in bytes), or 0 if the underlying algorithm is
     * not a block openSslEngine
     */
    @Override
    public int getBlockSize() {
        return CryptoCipherFactory.AES_BLOCK_SIZE;
    }

    /**
     * Initializes the openSslEngine with mode, key and iv.
     *
     * @param mode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
     * @param key crypto key for the openSslEngine
     * @param params the algorithm parameters
     * @throws InvalidKeyException If key length is invalid
     * @throws InvalidAlgorithmParameterException if IV length is wrong
     */
    @Override
    public void init(final int mode, final Key key, final AlgorithmParameterSpec params)
            throws InvalidKeyException, InvalidAlgorithmParameterException {
        Objects.requireNonNull(key, "key");
        Objects.requireNonNull(params, "params");

        final int cipherMode = mode == Cipher.ENCRYPT_MODE ? OpenSsl.ENCRYPT_MODE: OpenSsl.DECRYPT_MODE;
        openSslEngine.init(cipherMode, key.getEncoded(), params);
        initialized = true;
    }

    /**
     * Continues a multiple-part encryption/decryption operation. The data is
     * encrypted or decrypted, depending on how this openSslEngine was initialized.
     *
     * @param input the input byte array
     * @param inputOffset the offset in input where the input starts
     * @param inputLen the input length
     * @param output the byte array for the result
     * @param outputOffset the offset in output where the result is stored
     * @return the number of bytes stored in output
     * @throws ShortBufferException if there is insufficient space in the output
     *         byte array
     */
    @Override
    public int update(final byte[] input, final int inputOffset, final int inputLen,
            final byte[] output, final int outputOffset) throws ShortBufferException {
        return openSslEngine
                .update(input, inputOffset, inputLen, output, outputOffset);
    }


    /**
     * Continues a multiple-part encryption/decryption operation. The data is
     * encrypted or decrypted, depending on how this openSslEngine was initialized.
     *
     * @param inBuffer the input ByteBuffer
     * @param outBuffer the output ByteBuffer
     * @return int number of bytes stored in {@code output}
     * @throws ShortBufferException if there is insufficient space in the output
     *         buffer
     */
    @Override
    public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
            throws ShortBufferException {
        return openSslEngine.update(inBuffer, outBuffer);
    }

    /**
     * Continues a multi-part update of the Additional Authentication
     * Data (AAD).
     * <p>
     * Calls to this method provide AAD to the opensslEngine when operating in
     * modes such as AEAD (GCM).  If this opensslEngine is operating in
     * either GCM mode, all AAD must be supplied before beginning
     * operations on the ciphertext (via the {@code update} and
     * {@code doFinal} methods).
     * </p>
     *
     * @param aad the buffer containing the Additional Authentication Data
     *
     * @throws IllegalArgumentException if the {@code aad}
     * byte array is null
     * @throws IllegalStateException if this opensslEngine is in a wrong state
     * (e.g., has not been initialized), does not accept AAD, or if
     * operating in either GCM mode and one of the {@code update}
     * methods has already been called for the active
     * encryption/decryption operation
     * @throws UnsupportedOperationException if the implementation {@code opensslEngine}
     * doesn't support this operation.
     */
    @Override
    public void updateAAD(final byte[] aad) throws IllegalArgumentException,
            IllegalStateException, UnsupportedOperationException {
        if (aad == null) {
            throw new IllegalArgumentException("aad buffer is null");
        }
        if (!initialized) {
            throw new IllegalStateException("Cipher not initialized");
        }
        if (aad.length == 0) {
            return;
        }

        openSslEngine.updateAAD(aad);
    }


    /**
     * Continues a multi-part update of the Additional Authentication
     * Data (AAD).
     * <p>
     * Calls to this method provide AAD to the opensslEngine when operating in
     * modes such as AEAD (GCM).  If this opensslEngine is operating in
     * either GCM mode, all AAD must be supplied before beginning
     * operations on the ciphertext (via the {@code update} and
     * {@code doFinal} methods).
     * </p>
     *
     * @param aad the buffer containing the Additional Authentication Data
     *
     * @throws IllegalArgumentException if the {@code aad}
     * byte array is null
     * @throws IllegalStateException if this opensslEngine is in a wrong state
     * (e.g., has not been initialized), does not accept AAD, or if
     * operating in either GCM mode and one of the {@code update}
     * methods has already been called for the active
     * encryption/decryption operation
     * @throws UnsupportedOperationException if the implementation {@code opensslEngine}
     * doesn't support this operation.
     */
    @Override
    public void updateAAD(final ByteBuffer aad) throws IllegalArgumentException,
            IllegalStateException, UnsupportedOperationException {
        if (aad == null) {
            throw new IllegalArgumentException("aad buffer is null");
        }
        if (!initialized) {
            throw new IllegalStateException("Cipher not initialized");
        }

        final int aadLen = aad.limit() - aad.position();
        if (aadLen == 0) {
            return;
        }
        final byte[] aadBytes = new byte[aadLen];
        aad.get(aadBytes);
        openSslEngine.updateAAD(aadBytes);
    }
}