View Javadoc
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  
20  import java.nio.ByteBuffer;
21  import java.security.GeneralSecurityException;
22  import java.security.InvalidAlgorithmParameterException;
23  import java.security.InvalidKeyException;
24  import java.security.Key;
25  import java.security.NoSuchAlgorithmException;
26  import java.security.spec.AlgorithmParameterSpec;
27  import java.util.Objects;
28  import java.util.Properties;
29  
30  import javax.crypto.BadPaddingException;
31  import javax.crypto.Cipher;
32  import javax.crypto.IllegalBlockSizeException;
33  import javax.crypto.ShortBufferException;
34  import javax.crypto.spec.IvParameterSpec;
35  
36  import org.apache.commons.crypto.cipher.CryptoCipher;
37  import org.apache.commons.crypto.cipher.CryptoCipherFactory;
38  import org.apache.commons.crypto.utils.Transformation;
39  
40  import com.sun.jna.NativeLong;
41  import com.sun.jna.ptr.PointerByReference;
42  
43  /**
44   * Implements the CryptoCipher using JNA into OpenSSL.
45   */
46  final class OpenSslJnaCipher implements CryptoCipher {
47  
48      /**
49       * AlgorithmMode of JNA. Currently only support AES/CTR/NoPadding.
50       */
51      private enum AlgorithmMode {
52          AES_CTR, AES_CBC;
53  
54          /**
55           * Gets the AlgorithmMode instance.
56           *
57           * @param algorithm the algorithm name
58           * @param mode      the mode name
59           * @return the AlgorithmMode instance
60           * @throws NoSuchAlgorithmException if the algorithm is not support
61           */
62          static AlgorithmMode get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
63              try {
64                  return AlgorithmMode.valueOf(algorithm + "_" + mode);
65              } catch (final Exception e) {
66                  throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
67              }
68          }
69      }
70      private PointerByReference algo;
71      private final PointerByReference context;
72      private final AlgorithmMode algorithmMode;
73      private final int padding;
74      private final String transformation;
75  
76      private final int IV_LENGTH = 16;
77  
78      /**
79       * Constructs a {@link CryptoCipher} using JNA into OpenSSL
80       *
81       * @param props          properties for OpenSSL cipher
82       * @param transformation transformation for OpenSSL cipher
83       * @throws GeneralSecurityException if OpenSSL cipher initialize failed
84       */
85      public OpenSslJnaCipher(final Properties props, final String transformation) // NOPMD
86              throws GeneralSecurityException {
87          if (!OpenSslJna.isEnabled()) {
88              throw new GeneralSecurityException("Could not enable JNA access", OpenSslJna.initialisationError());
89          }
90          this.transformation = transformation;
91          final Transformation transform = Transformation.parse(transformation);
92          algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode());
93  
94          if (algorithmMode != AlgorithmMode.AES_CBC && algorithmMode != AlgorithmMode.AES_CTR) {
95              throw new GeneralSecurityException("Unknown algorithm " + transform.getAlgorithm() + "_" + transform.getMode());
96          }
97  
98          padding = transform.getPadding().ordinal();
99          context = OpenSslNativeJna.EVP_CIPHER_CTX_new();
100 
101     }
102 
103     /**
104      * Closes the OpenSSL cipher. Clean the OpenSsl native context.
105      */
106     @Override
107     public void close() {
108         if (context != null) {
109             OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
110             // Freeing the context multiple times causes a JVM crash
111             // A work-round is to only free it at finalize time
112             // TODO is that sufficient?
113             // OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
114         }
115     }
116 
117     /**
118      * Encrypts or decrypts data in a single-part operation, or finishes a
119      * multiple-part operation.
120      *
121      * @param input        the input byte array
122      * @param inputOffset  the offset in input where the input starts
123      * @param inputLen     the input length
124      * @param output       the byte array for the result
125      * @param outputOffset the offset in output where the result is stored
126      * @return the number of bytes stored in output
127      * @throws ShortBufferException      if the given output byte array is too small
128      *                                   to hold the result
129      * @throws BadPaddingException       if this cipher is in decryption mode, and
130      *                                   (un)padding has been requested, but the
131      *                                   decrypted data is not bounded by the
132      *                                   appropriate padding bytes
133      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
134      *                                   padding has been requested (only in
135      *                                   encryption mode), and the total input
136      *                                   length of the data processed by this cipher
137      *                                   is not a multiple of block size; or if this
138      *                                   encryption algorithm is unable to process
139      *                                   the input data provided.
140      */
141     @Override
142     public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
143             final int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
144         final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
145         final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
146         return doFinal(inputBuf, outputBuf);
147     }
148 
149     /**
150      * Encrypts or decrypts data in a single-part operation, or finishes a
151      * multiple-part operation. The data is encrypted or decrypted, depending on how
152      * this cipher was initialized.
153      *
154      * @param inBuffer  the input ByteBuffer
155      * @param outBuffer the output ByteBuffer
156      * @return int number of bytes stored in {@code output}
157      * @throws BadPaddingException       if this cipher is in decryption mode, and
158      *                                   (un)padding has been requested, but the
159      *                                   decrypted data is not bounded by the
160      *                                   appropriate padding bytes
161      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
162      *                                   padding has been requested (only in
163      *                                   encryption mode), and the total input
164      *                                   length of the data processed by this cipher
165      *                                   is not a multiple of block size; or if this
166      *                                   encryption algorithm is unable to process
167      *                                   the input data provided.
168      * @throws ShortBufferException      if the given output buffer is too small to
169      *                                   hold the result
170      */
171     @Override
172     public int doFinal(final ByteBuffer inBuffer, final ByteBuffer outBuffer)
173             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
174         final int uptLen = update(inBuffer, outBuffer);
175         final int[] outlen = new int[1];
176         throwOnError(OpenSslNativeJna.EVP_CipherFinal_ex(context, outBuffer, outlen));
177         final int len = uptLen + outlen[0];
178         outBuffer.position(outBuffer.position() + outlen[0]);
179         return len;
180     }
181 
182     @Override
183     protected void finalize() throws Throwable {
184         OpenSslNativeJna.EVP_CIPHER_CTX_free(context);
185         super.finalize();
186     }
187 
188     @Override
189     public String getAlgorithm() {
190         return transformation;
191     }
192 
193     @Override
194     public int getBlockSize() {
195         return CryptoCipherFactory.AES_BLOCK_SIZE;
196     }
197 
198     /**
199      * Initializes the cipher with mode, key and iv.
200      *
201      * @param mode   {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
202      * @param key    crypto key for the cipher
203      * @param params the algorithm parameters
204      * @throws InvalidKeyException                If key length is invalid
205      * @throws InvalidAlgorithmParameterException if IV length is wrong
206      */
207     @Override
208     public void init(final int mode, final Key key, final AlgorithmParameterSpec params)
209             throws InvalidKeyException, InvalidAlgorithmParameterException {
210         Objects.requireNonNull(key, "key");
211         Objects.requireNonNull(params, "params");
212         final int cipherMode = mode == Cipher.ENCRYPT_MODE ? OpenSslNativeJna.OOSL_JNA_ENCRYPT_MODE : OpenSslNativeJna.OOSL_JNA_DECRYPT_MODE;
213         if (!(params instanceof IvParameterSpec)) {
214             // other AlgorithmParameterSpec such as GCMParameterSpec is not
215             // supported now.
216             throw new InvalidAlgorithmParameterException("Illegal parameters");
217         }
218         final byte[] iv = ((IvParameterSpec) params).getIV();
219 
220         if ((algorithmMode == AlgorithmMode.AES_CBC || algorithmMode == AlgorithmMode.AES_CTR) && iv.length != IV_LENGTH) {
221             throw new InvalidAlgorithmParameterException("Wrong IV length: must be 16 bytes long");
222         }
223         final int keyEncodedLength = key.getEncoded().length;
224 
225         if (algorithmMode == AlgorithmMode.AES_CBC) {
226             switch (keyEncodedLength) {
227             case 16:
228                 algo = OpenSslNativeJna.EVP_aes_128_cbc();
229                 break;
230             case 24:
231                 algo = OpenSslNativeJna.EVP_aes_192_cbc();
232                 break;
233             case 32:
234                 algo = OpenSslNativeJna.EVP_aes_256_cbc();
235                 break;
236             default:
237                 throw new InvalidKeyException("keysize unsupported (" + keyEncodedLength + ")");
238             }
239 
240         } else {
241             switch (keyEncodedLength) {
242             case 16:
243                 algo = OpenSslNativeJna.EVP_aes_128_ctr();
244                 break;
245             case 24:
246                 algo = OpenSslNativeJna.EVP_aes_192_ctr();
247                 break;
248             case 32:
249                 algo = OpenSslNativeJna.EVP_aes_256_ctr();
250                 break;
251             default:
252                 throw new InvalidKeyException("keysize unsupported (" + keyEncodedLength + ")");
253             }
254         }
255 
256         throwOnError(OpenSslNativeJna.EVP_CipherInit_ex(context, algo, null, key.getEncoded(), iv, cipherMode));
257         throwOnError(OpenSslNativeJna.EVP_CIPHER_CTX_set_padding(context, padding));
258     }
259 
260     /**
261      * @param retVal the result value of error.
262      */
263     private void throwOnError(final int retVal) {
264         if (retVal != 1) {
265             final NativeLong err = OpenSslNativeJna.ERR_peek_error();
266             final String errdesc = OpenSslNativeJna.ERR_error_string(err, null);
267 
268             if (context != null) {
269                 OpenSslNativeJna.EVP_CIPHER_CTX_cleanup(context);
270             }
271             throw new IllegalStateException(
272                     "return code " + retVal + " from OpenSSL. Err code is " + err + ": " + errdesc);
273         }
274     }
275 
276     /**
277      * Continues a multiple-part encryption/decryption operation. The data is
278      * encrypted or decrypted, depending on how this cipher was initialized.
279      *
280      * @param input        the input byte array
281      * @param inputOffset  the offset in input where the input starts
282      * @param inputLen     the input length
283      * @param output       the byte array for the result
284      * @param outputOffset the offset in output where the result is stored
285      * @return the number of bytes stored in output
286      * @throws ShortBufferException if there is insufficient space in the output
287      *                              byte array
288      */
289     @Override
290     public int update(final byte[] input, final int inputOffset, final int inputLen, final byte[] output,
291             final int outputOffset) throws ShortBufferException {
292         final ByteBuffer outputBuf = ByteBuffer.wrap(output, outputOffset, output.length - outputOffset);
293         final ByteBuffer inputBuf = ByteBuffer.wrap(input, inputOffset, inputLen);
294         return update(inputBuf, outputBuf);
295     }
296 
297     /**
298      * Continues a multiple-part encryption/decryption operation. The data is
299      * encrypted or decrypted, depending on how this cipher was initialized.
300      *
301      * @param inBuffer  the input ByteBuffer
302      * @param outBuffer the output ByteBuffer
303      * @return int number of bytes stored in {@code output}
304      * @throws ShortBufferException if there is insufficient space in the output
305      *                              buffer
306      */
307     @Override
308     public int update(final ByteBuffer inBuffer, final ByteBuffer outBuffer) throws ShortBufferException {
309         final int[] outlen = new int[1];
310         throwOnError(OpenSslNativeJna.EVP_CipherUpdate(context, outBuffer, outlen, inBuffer, inBuffer.remaining()));
311         final int len = outlen[0];
312         inBuffer.position(inBuffer.limit());
313         outBuffer.position(outBuffer.position() + len);
314         return len;
315     }
316 
317     /**
318      * Continues a multi-part update of the Additional Authentication Data (AAD).
319      * <p>
320      * Calls to this method provide AAD to the opensslEngine when operating in modes
321      * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode,
322      * all AAD must be supplied before beginning operations on the ciphertext (via
323      * the {@code update} and {@code doFinal} methods).
324      * </p>
325      *
326      * @param aad the buffer containing the Additional Authentication Data
327      *
328      * @throws IllegalArgumentException      if the {@code aad} byte array is null
329      * @throws IllegalStateException         if this opensslEngine is in a wrong
330      *                                       state (e.g., has not been initialized),
331      *                                       does not accept AAD, or if operating in
332      *                                       either GCM mode and one of the
333      *                                       {@code update} methods has already been
334      *                                       called for the active
335      *                                       encryption/decryption operation
336      * @throws UnsupportedOperationException if the implementation
337      *                                       {@code opensslEngine} doesn't support
338      *                                       this operation.
339      */
340     @Override
341     public void updateAAD(final byte[] aad)
342             throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
343         // TODO: implement GCM mode using Jna
344         throw new UnsupportedOperationException("This is unsupported in Jna Cipher");
345     }
346 
347     /**
348      * Continues a multi-part update of the Additional Authentication Data (AAD).
349      * <p>
350      * Calls to this method provide AAD to the opensslEngine when operating in modes
351      * such as AEAD (GCM). If this opensslEngine is operating in either GCM mode,
352      * all AAD must be supplied before beginning operations on the ciphertext (via
353      * the {@code update} and {@code doFinal} methods).
354      * </p>
355      *
356      * @param aad the buffer containing the Additional Authentication Data
357      *
358      * @throws IllegalArgumentException      if the {@code aad} byte array is null
359      * @throws IllegalStateException         if this opensslEngine is in a wrong
360      *                                       state (e.g., has not been initialized),
361      *                                       does not accept AAD, or if operating in
362      *                                       either GCM mode and one of the
363      *                                       {@code update} methods has already been
364      *                                       called for the active
365      *                                       encryption/decryption operation
366      * @throws UnsupportedOperationException if the implementation
367      *                                       {@code opensslEngine} doesn't support
368      *                                       this operation.
369      */
370     @Override
371     public void updateAAD(final ByteBuffer aad)
372             throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
373         // TODO: implement GCM mode using Jna
374         throw new UnsupportedOperationException("This is unsupported in Jna Cipher");
375     }
376 }