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.cipher;
19  
20  import java.nio.ByteBuffer;
21  import java.security.InvalidAlgorithmParameterException;
22  import java.security.NoSuchAlgorithmException;
23  import java.security.spec.AlgorithmParameterSpec;
24  
25  import javax.crypto.BadPaddingException;
26  import javax.crypto.IllegalBlockSizeException;
27  import javax.crypto.NoSuchPaddingException;
28  import javax.crypto.ShortBufferException;
29  
30  import org.apache.commons.crypto.Crypto;
31  import org.apache.commons.crypto.utils.Transformation;
32  import org.apache.commons.crypto.utils.Utils;
33  
34  /**
35   * OpenSSL cryptographic wrapper using JNI. Currently only AES-CTR is supported.
36   * It's flexible to add other crypto algorithms/modes.
37   */
38  final class OpenSsl {
39  
40      /** Currently only support AES/CTR/NoPadding. */
41      private enum AlgorithmMode {
42          AES_CTR, AES_CBC, AES_GCM;
43  
44          /**
45           * Gets the mode.
46           *
47           * @param algorithm the algorithm.
48           * @param mode the mode.
49           * @return the Algorithm mode.
50           * @throws NoSuchAlgorithmException if the algorithm is not available.
51           */
52          static int get(final String algorithm, final String mode) throws NoSuchAlgorithmException {
53              try {
54                  return AlgorithmMode.valueOf(algorithm + "_" + mode).ordinal();
55              } catch (final Exception e) {
56                  throw new NoSuchAlgorithmException("Algorithm not supported: " + algorithm + " and mode: " + mode);
57              }
58          }
59      }
60      // Mode constant defined by OpenSsl JNI
61      public static final int ENCRYPT_MODE = 1;
62  
63      public static final int DECRYPT_MODE = 0;
64  
65      private static final Throwable loadingFailureReason;
66  
67      static {
68          Throwable loadingFailure = null;
69          try {
70              if (Crypto.isNativeCodeLoaded()) {
71                  OpenSslNative.initIDs();
72              } else {
73                  loadingFailure = Crypto.getLoadingError();
74              }
75          } catch (final Exception | UnsatisfiedLinkError t) {
76              loadingFailure = t;
77          } finally {
78              loadingFailureReason = loadingFailure;
79          }
80      }
81  
82      /**
83       * Gets an {@code OpenSslCipher} that implements the specified
84       * transformation.
85       *
86       * @param transformation the name of the transformation, e.g.,
87       *        AES/CTR/NoPadding.
88       * @return OpenSslCipher an {@code OpenSslCipher} object
89       * @throws NoSuchAlgorithmException if {@code transformation} is null,
90       *         empty, in an invalid format, or if OpenSsl doesn't implement the
91       *         specified algorithm.
92       * @throws NoSuchPaddingException if {@code transformation} contains a
93       *         padding scheme that is not available.
94       * @throws IllegalStateException if native code cannot be initialized
95       */
96      public static OpenSsl getInstance(final String transformation)
97              throws NoSuchAlgorithmException, NoSuchPaddingException {
98          if (loadingFailureReason != null) {
99              throw new IllegalStateException(loadingFailureReason);
100         }
101         final Transformation transform = Transformation.parse(transformation);
102         final int algorithmMode = AlgorithmMode.get(transform.getAlgorithm(), transform.getMode());
103         final int padding = transform.getPadding().ordinal();
104         final long context = OpenSslNative.initContext(algorithmMode, padding);
105         return new OpenSsl(context, algorithmMode, padding);
106     }
107 
108     /**
109      * Gets the failure reason when loading OpenSsl native.
110      *
111      * @return the failure reason; null if it was loaded and initialized successfully
112      */
113     public static Throwable getLoadingFailureReason() {
114         return loadingFailureReason;
115     }
116 
117     private final AbstractOpenSslFeedbackCipher opensslBlockCipher;
118 
119     /**
120      * Constructs a {@link OpenSsl} instance based on context, algorithm and padding.
121      *
122      * @param context the context.
123      * @param algorithm the algorithm.
124      * @param padding the padding.
125      */
126     private OpenSsl(final long context, final int algorithm, final int padding) {
127         if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
128             opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding);
129         } else {
130             opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding);
131         }
132     }
133 
134     /** Forcibly clean the context. */
135     public void clean() {
136         if (opensslBlockCipher != null) {
137             opensslBlockCipher.clean();
138         }
139     }
140 
141     /**
142      * Finalizes to encrypt or decrypt data in a single-part operation, or finishes a
143      * multiple-part operation.
144      *
145      * @param input the input byte array
146      * @param inputOffset the offset in input where the input starts
147      * @param inputLen the input length
148      * @param output the byte array for the result
149      * @param outputOffset the offset in output where the result is stored
150      * @return the number of bytes stored in output
151      * @throws ShortBufferException if the given output byte array is too small
152      *         to hold the result
153      * @throws BadPaddingException if this cipher is in decryption mode, and
154      *         (un)padding has been requested, but the decrypted data is not
155      *         bounded by the appropriate padding bytes
156      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
157      *         padding has been requested (only in encryption mode), and the
158      *         total input length of the data processed by this cipher is not a
159      *         multiple of block size; or if this encryption algorithm is unable
160      *         to process the input data provided.
161      */
162     public int doFinal(final byte[] input, final int inputOffset, final int inputLen, final byte[] output, final int outputOffset)
163             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
164         return opensslBlockCipher.doFinal(input, inputOffset, inputLen, output, outputOffset);
165     }
166 
167     /**
168      * Finishes a multiple-part operation. The data is encrypted or decrypted,
169      * depending on how this cipher was initialized.
170      *
171      * <p>
172      * The result is stored in the output buffer. Upon return, the output
173      * buffer's position will have advanced by n, where n is the value returned
174      * by this method; the output buffer's limit will not have changed.
175      * </p>
176      *
177      * <p>
178      * If {@code output.remaining()} bytes are insufficient to hold the
179      * result, a {@code ShortBufferException} is thrown.
180      * </p>
181      *
182      * <p>
183      * Upon finishing, this method resets this cipher object to the state it was
184      * in when previously initialized. That is, the object is available to
185      * encrypt or decrypt more data.
186      * </p>
187      *
188      * If any exception is thrown, this cipher object need to be reset before it
189      * can be used again.
190      *
191      * @param input the input ByteBuffer
192      * @param output the output ByteBuffer
193      * @return int number of bytes stored in {@code output}
194      * @throws ShortBufferException if the given output byte array is too small
195      *         to hold the result.
196      * @throws IllegalBlockSizeException if this cipher is a block cipher, no
197      *         padding has been requested (only in encryption mode), and the
198      *         total input length of the data processed by this cipher is not a
199      *         multiple of block size; or if this encryption algorithm is unable
200      *         to process the input data provided.
201      * @throws BadPaddingException if this cipher is in decryption mode, and
202      *         (un)padding has been requested, but the decrypted data is not
203      *         bounded by the appropriate padding bytes
204      */
205     public int doFinal(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
206         Utils.checkArgument(output.isDirect(), "Direct buffer is required.");
207 
208         return opensslBlockCipher.doFinal(input, output);
209     }
210 
211     @Override
212     protected void finalize() throws Throwable {
213         clean();
214     }
215 
216     /**
217      * Initializes this cipher with a key and IV.
218      *
219      * @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
220      * @param key crypto key
221      * @param params the algorithm parameters
222      * @throws InvalidAlgorithmParameterException if IV length is wrong
223      */
224     public void init(final int mode, final byte[] key, final AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
225         opensslBlockCipher.init(mode, key, params);
226     }
227 
228     /**
229      * Updates a multiple-part encryption/decryption operation. The data is
230      * encrypted or decrypted, depending on how this cipher was initialized.
231      *
232      * @param input the input byte array
233      * @param inputOffset the offset in input where the input starts
234      * @param inputLen the input length
235      * @param output the byte array for the result
236      * @param outputOffset the offset in output where the result is stored
237      * @return the number of bytes stored in output
238      * @throws ShortBufferException if there is insufficient space in the output
239      *         byte array
240      */
241     public int update(final byte[] input, final int inputOffset, final int inputLen,
242             final byte[] output, final int outputOffset) throws ShortBufferException {
243         return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset);
244     }
245 
246 
247     /**
248      * Updates a multiple-part encryption or decryption operation. The data is
249      * encrypted or decrypted, depending on how this cipher was initialized.
250      *
251      * <p>
252      * All {@code input.remaining()} bytes starting at
253      * {@code input.position()} are processed. The result is stored in the
254      * output buffer.
255      * </p>
256      *
257      * <p>
258      * Upon return, the input buffer's position will be equal to its limit; its
259      * limit will not have changed. The output buffer's position will have
260      * advanced by n, when n is the value returned by this method; the output
261      * buffer's limit will not have changed.
262      * </p>
263      *
264      * If {@code output.remaining()} bytes are insufficient to hold the
265      * result, a {@code ShortBufferException} is thrown.
266      *
267      * @param input the input ByteBuffer
268      * @param output the output ByteBuffer
269      * @return int number of bytes stored in {@code output}
270      * @throws ShortBufferException if there is insufficient space in the output
271      *         buffer
272      */
273     public int update(final ByteBuffer input, final ByteBuffer output) throws ShortBufferException {
274         Utils.checkArgument(input.isDirect() && output.isDirect(), "Direct buffers are required.");
275         return opensslBlockCipher.update(input, output);
276     }
277 
278     /**
279      * Continues a multi-part update of the Additional Authentication
280      * Data (AAD).
281      * <p>
282      * Calls to this method provide AAD to the cipher when operating in
283      * modes such as AEAD (GCM).  If this cipher is operating in
284      * either GCM mode, all AAD must be supplied before beginning
285      * operations on the ciphertext (via the {@code update} and
286      * {@code doFinal} methods).
287      * </p>
288      *
289      * @param aad the buffer containing the Additional Authentication Data
290      */
291     public void updateAAD(final byte[] aad) {
292         this.opensslBlockCipher.updateAAD(aad);
293     }
294 
295 }