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 }