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 }