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