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 }