OpenSslJnaCipher.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.jna;

  19. import java.nio.ByteBuffer;
  20. import java.security.GeneralSecurityException;
  21. import java.security.InvalidAlgorithmParameterException;
  22. import java.security.InvalidKeyException;
  23. import java.security.Key;
  24. import java.security.NoSuchAlgorithmException;
  25. import java.security.spec.AlgorithmParameterSpec;
  26. import java.util.Objects;
  27. import java.util.Properties;

  28. import javax.crypto.BadPaddingException;
  29. import javax.crypto.Cipher;
  30. import javax.crypto.IllegalBlockSizeException;
  31. import javax.crypto.ShortBufferException;
  32. import javax.crypto.spec.IvParameterSpec;

  33. import org.apache.commons.crypto.cipher.CryptoCipher;
  34. import org.apache.commons.crypto.cipher.CryptoCipherFactory;
  35. import org.apache.commons.crypto.utils.Transformation;

  36. import com.sun.jna.NativeLong;
  37. import com.sun.jna.ptr.PointerByReference;

  38. /**
  39.  * Implements the CryptoCipher using JNA into OpenSSL.
  40.  */
  41. final class OpenSslJnaCipher implements CryptoCipher {

  42.     /**
  43.      * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding.
  44.      */
  45.     private enum AlgorithmMode {
  46.         AES_CTR, AES_CBC;

  47.         /**
  48.          * Gets the AlgorithmMode instance.
  49.          *
  50.          * @param algorithm the algorithm name
  51.          * @param mode      the mode name
  52.          * @return the AlgorithmMode instance
  53.          * @throws NoSuchAlgorithmException if the algorithm is not support
  54.          */
  55.         static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
  56.             try {
  57.                 return AlgorithmMode.valueOf(algorithm + "_" + mode);
  58.             } catch (final Exception e) {
  59.                 throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
  60.             }
  61.         }
  62.     }
  63.     private PointerByReference algo;
  64.     private final PointerByReference context;
  65.     private final AlgorithmMode algorithmMode;
  66.     private final int padding;
  67.     private final String transformation;

  68.     private final int IV_LENGTH = 16;

  69.     /**
  70.      * Constructs a {@link CryptoCipher} using JNA into OpenSSL
  71.      *
  72.      * @param props          properties for OpenSSL cipher
  73.      * @param transformation transformation for OpenSSL cipher
  74.      * @throws GeneralSecurityException if OpenSSL cipher initialize failed
  75.      */
  76.     public OpenSslJnaCipher(final Properties props, final String transformation) // NOPMD
  77.             throws GeneralSecurityException {
  78.         if (!OpenSslJna.isEnabled()) {
  79.             throw new GeneralSecurityException("Could not enable JNA access", OpenSslJna.initialisationError());
  80.         }
  81.         this.transformation = transformation;
  82.         final Transformation transform = Transformation.parse(transformation);
  83.         algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode());

  84.         if (algorithmMode != AlgorithmMode.AES_CBC && algorithmMode != AlgorithmMode.AES_CTR) {
  85.             throw new GeneralSecurityException("Unknown algorithm " + transform.getAlgorithm() + "_" + transform.getMode());
  86.         }

  87.         padding = transform.getPadding().ordinal();
  88.         context = OpenSslNativeJna.EVP_CIPHER_CTX_new();

  89.     }

  90.     /**
  91.      * Closes the OpenSSL cipher. Clean the OpenSsl native context.
  92.      */
  93.     @Override
  94.     public void close() {
  95.         if (context != null) {
  96.             OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
  97.             // Freeing the context multiple times causes a JVM crash
  98.             // A work-round is to only free it at finalize time
  99.             // TODO is that sufficient?
  100.             // OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
  101.         }
  102.     }

  103.     /**
  104.      * Encrypts or decrypts data in a single-part operation, or finishes a
  105.      * multiple-part operation.
  106.      *
  107.      * @param input        the input byte array
  108.      * @param inputOffset  the offset in input where the input starts
  109.      * @param inputLen     the input length
  110.      * @param output       the byte array for the result
  111.      * @param outputOffset the offset in output where the result is stored
  112.      * @return the number of bytes stored in output
  113.      * @throws ShortBufferException      if the given output byte array is too small
  114.      *                                   to hold the result
  115.      * @throws BadPaddingException       if this cipher is in decryption mode, and
  116.      *                                   (un)padding has been requested, but the
  117.      *                                   decrypted data is not bounded by the
  118.      *                                   appropriate padding bytes
  119.      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
  120.      *                                   padding has been requested (only in
  121.      *                                   encryption mode), and the total input
  122.      *                                   length of the data processed by this cipher
  123.      *                                   is not a multiple of block size; or if this
  124.      *                                   encryption algorithm is unable to process
  125.      *                                   the input data provided.
  126.      */
  127.     @Override
  128.     public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
  129.             final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
  130.         final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
  131.         final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
  132.         return doFinal(inputBuf, outputBuf);
  133.     }

  134.     /**
  135.      * Encrypts or decrypts data in a single-part operation, or finishes a
  136.      * multiple-part operation. The data is encrypted or decrypted, depending on how
  137.      * this cipher was initialized.
  138.      *
  139.      * @param inBuffer  the input ByteBuffer
  140.      * @param outBuffer the output ByteBuffer
  141.      * @return int number of bytes stored in {@code output}
  142.      * @throws BadPaddingException       if this cipher is in decryption mode, and
  143.      *                                   (un)padding has been requested, but the
  144.      *                                   decrypted data is not bounded by the
  145.      *                                   appropriate padding bytes
  146.      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
  147.      *                                   padding has been requested (only in
  148.      *                                   encryption mode), and the total input
  149.      *                                   length of the data processed by this cipher
  150.      *                                   is not a multiple of block size; or if this
  151.      *                                   encryption algorithm is unable to process
  152.      *                                   the input data provided.
  153.      * @throws ShortBufferException      if the given output buffer is too small to
  154.      *                                   hold the result
  155.      */
  156.     @Override
  157.     public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
  158.             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
  159.         final int uptLen = update(inBuffer, outBuffer);
  160.         final int[] outlen = new int[1];
  161.         throwOnError(OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen));
  162.         final int len = uptLen + outlen[0];
  163.         outBuffer.position(outBuffer.position() + outlen[0]);
  164.         return len;
  165.     }

  166.     @Override
  167.     protected void finalize() throws Throwable {
  168.         OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
  169.         super.finalize();
  170.     }

  171.     @Override
  172.     public String getAlgorithm() {
  173.         return transformation;
  174.     }

  175.     @Override
  176.     public int getBlockSize() {
  177.         return CryptoCipherFactory.AES_BLOCK_SIZE;
  178.     }

  179.     /**
  180.      * Initializes the cipher with mode, key and iv.
  181.      *
  182.      * @param mode   {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
  183.      * @param key    crypto key for the cipher
  184.      * @param params the algorithm parameters
  185.      * @throws InvalidKeyException                If key length is invalid
  186.      * @throws InvalidAlgorithmParameterException if IV length is wrong
  187.      */
  188.     @Override
  189.     public void init(final int mode, final Key key, final AlgorithmParameterSpec params)
  190.             throws InvalidKeyException, InvalidAlgorithmParameterException {
  191.         Objects.requireNonNull(key, "key");
  192.         Objects.requireNonNull(params, "params");
  193.         final int cipherMode = mode == Cipher.ENCRYPT_MODE ? OpenSslNativeJna.OOSL_JNA_ENCRYPT_MODE : OpenSslNativeJna.OOSL_JNA_DECRYPT_MODE;
  194.         if (!(params instanceof IvParameterSpec)) {
  195.             // other AlgorithmParameterSpec such as GCMParameterSpec is not
  196.             // supported now.
  197.             throw new InvalidAlgorithmParameterException("Illegal parameters");
  198.         }
  199.         final byte[] iv = ((IvParameterSpec) params).getIV();

  200.         if ((algorithmMode == AlgorithmMode.AES_CBC || algorithmMode == AlgorithmMode.AES_CTR) && iv.length != IV_LENGTH) {
  201.             throw new InvalidAlgorithmParameterException("Wrong IV length: must be 16 bytes long");
  202.         }
  203.         final int keyEncodedLength = key.getEncoded().length;

  204.         if (algorithmMode == AlgorithmMode.AES_CBC) {
  205.             switch (keyEncodedLength) {
  206.             case 16:
  207.                 algo = OpenSslNativeJna.EVP_aes_128_cbc();
  208.                 break;
  209.             case 24:
  210.                 algo = OpenSslNativeJna.EVP_aes_192_cbc();
  211.                 break;
  212.             case 32:
  213.                 algo = OpenSslNativeJna.EVP_aes_256_cbc();
  214.                 break;
  215.             default:
  216.                 throw new InvalidKeyException("keysize unsupported (" + keyEncodedLength + ")");
  217.             }

  218.         } else {
  219.             switch (keyEncodedLength) {
  220.             case 16:
  221.                 algo = OpenSslNativeJna.EVP_aes_128_ctr();
  222.                 break;
  223.             case 24:
  224.                 algo = OpenSslNativeJna.EVP_aes_192_ctr();
  225.                 break;
  226.             case 32:
  227.                 algo = OpenSslNativeJna.EVP_aes_256_ctr();
  228.                 break;
  229.             default:
  230.                 throw new InvalidKeyException("keysize unsupported (" + keyEncodedLength + ")");
  231.             }
  232.         }

  233.         throwOnError(OpenSslNativeJna.EVP_CipherInit_ex(context, algo, null, key.getEncoded(), iv, cipherMode));
  234.         throwOnError(OpenSslNativeJna.EVP_CIPHER_CTX_set_padding(context, padding));
  235.     }

  236.     /**
  237.      * @param retVal the result value of error.
  238.      */
  239.     private void throwOnError(final int retVal) {
  240.         if (retVal != 1) {
  241.             final NativeLong err = OpenSslNativeJna.ERR_peek_error();
  242.             final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);

  243.             if (context != null) {
  244.                 OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
  245.             }
  246.             throw new IllegalStateException(
  247.                     "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
  248.         }
  249.     }

  250.     /**
  251.      * Continues a multiple-part encryption/decryption operation. The data is
  252.      * encrypted or decrypted, depending on how this cipher was initialized.
  253.      *
  254.      * @param input        the input byte array
  255.      * @param inputOffset  the offset in input where the input starts
  256.      * @param inputLen     the input length
  257.      * @param output       the byte array for the result
  258.      * @param outputOffset the offset in output where the result is stored
  259.      * @return the number of bytes stored in output
  260.      * @throws ShortBufferException if there is insufficient space in the output
  261.      *                              byte array
  262.      */
  263.     @Override
  264.     public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
  265.             final int outputOffset) throws ShortBufferException {
  266.         final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
  267.         final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
  268.         return update(inputBuf, outputBuf);
  269.     }

  270.     /**
  271.      * Continues a multiple-part encryption/decryption operation. The data is
  272.      * encrypted or decrypted, depending on how this cipher was initialized.
  273.      *
  274.      * @param inBuffer  the input ByteBuffer
  275.      * @param outBuffer the output ByteBuffer
  276.      * @return int number of bytes stored in {@code output}
  277.      * @throws ShortBufferException if there is insufficient space in the output
  278.      *                              buffer
  279.      */
  280.     @Override
  281.     public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
  282.         final int[] outlen = new int[1];
  283.         throwOnError(OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, inBuffer.remaining()));
  284.         final int len = outlen[0];
  285.         inBuffer.position(inBuffer.limit());
  286.         outBuffer.position(outBuffer.position() + len);
  287.         return len;
  288.     }

  289.     /**
  290.      * Continues a multi-part update of the Additional Authentication Data (AAD).
  291.      * <p>
  292.      * Calls to this method provide AAD to the opensslEngine when operating in modes
  293.      * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode,
  294.      * all AAD must be supplied before beginning operations on the ciphertext (via
  295.      * the {@code update} and {@code doFinal} methods).
  296.      * </p>
  297.      *
  298.      * @param aad the buffer containing the Additional Authentication Data
  299.      *
  300.      * @throws IllegalArgumentException      if the {@code aad} byte array is null
  301.      * @throws IllegalStateException         if this opensslEngine is in a wrong
  302.      *                                       state (e.g., has not been initialized),
  303.      *                                       does not accept AAD, or if operating in
  304.      *                                       either GCM mode and one of the
  305.      *                                       {@code update} methods has already been
  306.      *                                       called for the active
  307.      *                                       encryption/decryption operation
  308.      * @throws UnsupportedOperationException if the implementation
  309.      *                                       {@code opensslEngine} doesn't support
  310.      *                                       this operation.
  311.      */
  312.     @Override
  313.     public void updateAAD(final byte[] aad)
  314.             throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
  315.         // TODO: implement GCM mode using Jna
  316.         throw new UnsupportedOperationException("This is unsupported in Jna Cipher");
  317.     }

  318.     /**
  319.      * Continues a multi-part update of the Additional Authentication Data (AAD).
  320.      * <p>
  321.      * Calls to this method provide AAD to the opensslEngine when operating in modes
  322.      * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode,
  323.      * all AAD must be supplied before beginning operations on the ciphertext (via
  324.      * the {@code update} and {@code doFinal} methods).
  325.      * </p>
  326.      *
  327.      * @param aad the buffer containing the Additional Authentication Data
  328.      *
  329.      * @throws IllegalArgumentException      if the {@code aad} byte array is null
  330.      * @throws IllegalStateException         if this opensslEngine is in a wrong
  331.      *                                       state (e.g., has not been initialized),
  332.      *                                       does not accept AAD, or if operating in
  333.      *                                       either GCM mode and one of the
  334.      *                                       {@code update} methods has already been
  335.      *                                       called for the active
  336.      *                                       encryption/decryption operation
  337.      * @throws UnsupportedOperationException if the implementation
  338.      *                                       {@code opensslEngine} doesn't support
  339.      *                                       this operation.
  340.      */
  341.     @Override
  342.     public void updateAAD(final ByteBuffer aad)
  343.             throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
  344.         // TODO: implement GCM mode using Jna
  345.         throw new UnsupportedOperationException("This is unsupported in Jna Cipher");
  346.     }
  347. }