OpenSsl.java

  1.  /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *     http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing, software
  13.  * distributed under the License is distributed on an "AS IS" BASIS,
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  * See the License for the specific language governing permissions and
  16.  * limitations under the License.
  17.  */
  18. package org.apache.commons.crypto.cipher;

  19. import java.nio.ByteBuffer;
  20. import java.security.InvalidAlgorithmParameterException;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.security.spec.AlgorithmParameterSpec;

  23. import javax.crypto.BadPaddingException;
  24. import javax.crypto.IllegalBlockSizeException;
  25. import javax.crypto.NoSuchPaddingException;
  26. import javax.crypto.ShortBufferException;

  27. import org.apache.commons.crypto.Crypto;
  28. import org.apache.commons.crypto.utils.Transformation;
  29. import org.apache.commons.crypto.utils.Utils;

  30. /**
  31.  * OpenSSL cryptographic wrapper using JNI. Currently only AES-CTR is supported.
  32.  * It's flexible to add other crypto algorithms/modes.
  33.  */
  34. final class OpenSsl {

  35.     /** Currently only support AES/CTR/NoPadding. */
  36.     private enum AlgorithmMode {
  37.         AES_CTR, AES_CBC, AES_GCM;

  38.         /**
  39.          * Gets the mode.
  40.          *
  41.          * @param algorithm the algorithm.
  42.          * @param mode the mode.
  43.          * @return the Algorithm mode.
  44.          * @throws NoSuchAlgorithmException if the algorithm is not available.
  45.          */
  46.         static int get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
  47.             try {
  48.                 return AlgorithmMode.valueOf(algorithm + "_" + mode).ordinal();
  49.             } catch (final Exception e) {
  50.                 throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
  51.             }
  52.         }
  53.     }
  54.     // Mode constant defined by OpenSsl JNI
  55.     public static final int ENCRYPT_MODE = 1;

  56.     public static final int DECRYPT_MODE = 0;

  57.     private static final Throwable loadingFailureReason;

  58.     static {
  59.         Throwable loadingFailure = null;
  60.         try {
  61.             if (Crypto.isNativeCodeLoaded()) {
  62.                 OpenSslNative.initIDs();
  63.             } else {
  64.                 loadingFailure = Crypto.getLoadingError();
  65.             }
  66.         } catch (final Exception | UnsatisfiedLinkError t) {
  67.             loadingFailure = t;
  68.         } finally {
  69.             loadingFailureReason = loadingFailure;
  70.         }
  71.     }

  72.     /**
  73.      * Gets an {@code OpenSslCipher} that implements the specified
  74.      * transformation.
  75.      *
  76.      * @param transformation the name of the transformation, e.g.,
  77.      *        AES/CTR/NoPadding.
  78.      * @return OpenSslCipher an {@code OpenSslCipher} object
  79.      * @throws NoSuchAlgorithmException if {@code transformation} is null,
  80.      *         empty, in an invalid format, or if OpenSsl doesn't implement the
  81.      *         specified algorithm.
  82.      * @throws NoSuchPaddingException if {@code transformation} contains a
  83.      *         padding scheme that is not available.
  84.      * @throws IllegalStateException if native code cannot be initialized
  85.      */
  86.     public static OpenSsl getInstance(final String transformation)
  87.             throws NoSuchAlgorithmException, NoSuchPaddingException {
  88.         if (loadingFailureReason != null) {
  89.             throw new IllegalStateException(loadingFailureReason);
  90.         }
  91.         final Transformation transform = Transformation.parse(transformation);
  92.         final int algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode());
  93.         final int padding = transform.getPadding().ordinal();
  94.         final long context = OpenSslNative.initContext(algorithmMode, padding);
  95.         return new OpenSsl(context, algorithmMode, padding);
  96.     }

  97.     /**
  98.      * Gets the failure reason when loading OpenSsl native.
  99.      *
  100.      * @return the failure reason; null if it was loaded and initialized successfully
  101.      */
  102.     public static Throwable getLoadingFailureReason() {
  103.         return loadingFailureReason;
  104.     }

  105.     private final AbstractOpenSslFeedbackCipher opensslBlockCipher;

  106.     /**
  107.      * Constructs a {@link OpenSsl} instance based on context, algorithm and padding.
  108.      *
  109.      * @param context the context.
  110.      * @param algorithm the algorithm.
  111.      * @param padding the padding.
  112.      */
  113.     private OpenSsl(final long context, final int algorithm, final int padding) {
  114.         if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
  115.             opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding);
  116.         } else {
  117.             opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding);
  118.         }
  119.     }

  120.     /** Forcibly clean the context. */
  121.     public void clean() {
  122.         if (opensslBlockCipher != null) {
  123.             opensslBlockCipher.clean();
  124.         }
  125.     }

  126.     /**
  127.      * Finalizes to encrypt or decrypt data in a single-part operation, or finishes a
  128.      * multiple-part operation.
  129.      *
  130.      * @param input the input byte array
  131.      * @param inputOffset the offset in input where the input starts
  132.      * @param inputLen the input length
  133.      * @param output the byte array for the result
  134.      * @param outputOffset the offset in output where the result is stored
  135.      * @return the number of bytes stored in output
  136.      * @throws ShortBufferException if the given output byte array is too small
  137.      *         to hold the result
  138.      * @throws BadPaddingException if this cipher is in decryption mode, and
  139.      *         (un)padding has been requested, but the decrypted data is not
  140.      *         bounded by the appropriate padding bytes
  141.      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
  142.      *         padding has been requested (only in encryption mode), and the
  143.      *         total input length of the data processed by this cipher is not a
  144.      *         multiple of block size; or if this encryption algorithm is unable
  145.      *         to process the input data provided.
  146.      */
  147.     public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
  148.             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
  149.         return opensslBlockCipher.doFinal(input, inputOffset, inputLen, output, outputOffset);
  150.     }

  151.     /**
  152.      * Finishes a multiple-part operation. The data is encrypted or decrypted,
  153.      * depending on how this cipher was initialized.
  154.      *
  155.      * <p>
  156.      * The result is stored in the output buffer. Upon return, the output
  157.      * buffer's position will have advanced by n, where n is the value returned
  158.      * by this method; the output buffer's limit will not have changed.
  159.      * </p>
  160.      *
  161.      * <p>
  162.      * If {@code output.remaining()} bytes are insufficient to hold the
  163.      * result, a {@code ShortBufferException} is thrown.
  164.      * </p>
  165.      *
  166.      * <p>
  167.      * Upon finishing, this method resets this cipher object to the state it was
  168.      * in when previously initialized. That is, the object is available to
  169.      * encrypt or decrypt more data.
  170.      * </p>
  171.      *
  172.      * If any exception is thrown, this cipher object need to be reset before it
  173.      * can be used again.
  174.      *
  175.      * @param input the input ByteBuffer
  176.      * @param output the output ByteBuffer
  177.      * @return int number of bytes stored in {@code output}
  178.      * @throws ShortBufferException if the given output byte array is too small
  179.      *         to hold the result.
  180.      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
  181.      *         padding has been requested (only in encryption mode), and the
  182.      *         total input length of the data processed by this cipher is not a
  183.      *         multiple of block size; or if this encryption algorithm is unable
  184.      *         to process the input data provided.
  185.      * @throws BadPaddingException if this cipher is in decryption mode, and
  186.      *         (un)padding has been requested, but the decrypted data is not
  187.      *         bounded by the appropriate padding bytes
  188.      */
  189.     public int doFinal(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
  190.         Utils.checkArgument(output.isDirect(), "Direct buffer is required.");

  191.         return opensslBlockCipher.doFinal(input, output);
  192.     }

  193.     @Override
  194.     protected void finalize() throws Throwable {
  195.         clean();
  196.     }

  197.     /**
  198.      * Initializes this cipher with a key and IV.
  199.      *
  200.      * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
  201.      * @param key crypto key
  202.      * @param params the algorithm parameters
  203.      * @throws InvalidAlgorithmParameterException if IV length is wrong
  204.      */
  205.     public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
  206.         opensslBlockCipher.init(mode, key, params);
  207.     }

  208.     /**
  209.      * Updates a multiple-part encryption/decryption operation. The data is
  210.      * encrypted or decrypted, depending on how this cipher was initialized.
  211.      *
  212.      * @param input the input byte array
  213.      * @param inputOffset the offset in input where the input starts
  214.      * @param inputLen the input length
  215.      * @param output the byte array for the result
  216.      * @param outputOffset the offset in output where the result is stored
  217.      * @return the number of bytes stored in output
  218.      * @throws ShortBufferException if there is insufficient space in the output
  219.      *         byte array
  220.      */
  221.     public int update(final byte[] input, final int inputOffset, final int inputLen,
  222.             final byte[] output, final int outputOffset) throws ShortBufferException {
  223.         return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset);
  224.     }


  225.     /**
  226.      * Updates a multiple-part encryption or decryption operation. The data is
  227.      * encrypted or decrypted, depending on how this cipher was initialized.
  228.      *
  229.      * <p>
  230.      * All {@code input.remaining()} bytes starting at
  231.      * {@code input.position()} are processed. The result is stored in the
  232.      * output buffer.
  233.      * </p>
  234.      *
  235.      * <p>
  236.      * Upon return, the input buffer's position will be equal to its limit; its
  237.      * limit will not have changed. The output buffer's position will have
  238.      * advanced by n, when n is the value returned by this method; the output
  239.      * buffer's limit will not have changed.
  240.      * </p>
  241.      *
  242.      * If {@code output.remaining()} bytes are insufficient to hold the
  243.      * result, a {@code ShortBufferException} is thrown.
  244.      *
  245.      * @param input the input ByteBuffer
  246.      * @param output the output ByteBuffer
  247.      * @return int number of bytes stored in {@code output}
  248.      * @throws ShortBufferException if there is insufficient space in the output
  249.      *         buffer
  250.      */
  251.     public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
  252.         Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required.");
  253.         return opensslBlockCipher.update(input, output);
  254.     }

  255.     /**
  256.      * Continues a multi-part update of the Additional Authentication
  257.      * Data (AAD).
  258.      * <p>
  259.      * Calls to this method provide AAD to the cipher when operating in
  260.      * modes such as AEAD (GCM).  If this cipher is operating in
  261.      * either GCM mode, all AAD must be supplied before beginning
  262.      * operations on the ciphertext (via the {@code update} and
  263.      * {@code doFinal} methods).
  264.      * </p>
  265.      *
  266.      * @param aad the buffer containing the Additional Authentication Data
  267.      */
  268.     public void updateAAD(final byte[] aad) {
  269.         this.opensslBlockCipher.updateAAD(aad);
  270.     }

  271. }