diff --git a/Makefile.common b/Makefile.common index 0f50098c0..3518f572b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -88,7 +88,7 @@ Linux-x86_64_CXX := $(CROSS_PREFIX)g++ Linux-x86_64_STRIP := $(CROSS_PREFIX)strip Linux-x86_64_CXXFLAGS := -Ilib/inc_linux -I$(JAVA_HOME)/include -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 Linux-x86_64_CFLAGS := -Ilib/inc_linux -I$(JAVA_HOME)/include -Ilib/inc_mac -O2 -fPIC -fvisibility=hidden -m64 -Linux-x86_64_LINKFLAGS := -shared -static-libgcc -static-libstdc++ +Linux-x86_64_LINKFLAGS := -shared -static-libgcc Linux-x86_64_LIBNAME := libcommons-crypto.so Linux-x86_64_COMMONS_CRYPTO_FLAGS := diff --git a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java index f3bdbcd9d..668ef2fc9 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java +++ b/src/main/java/org/apache/commons/crypto/cipher/CryptoCipher.java @@ -153,4 +153,56 @@ int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer) int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException; + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the cipher when operating in + * modes such as AEAD (GCM). If this cipher is operating in + * GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the corresponding method + * has not been overridden by an implementation + * + */ + void updateAAD(byte[] aad) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException; + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the cipher when operating in + * modes such as AEAD (GCM). If this cipher is operating in + * GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the corresponding method + * has not been overridden by an implementation + * + */ + void updateAAD(ByteBuffer aad) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException; } diff --git a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java index a6b64fd35..0df3f4bb6 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java +++ b/src/main/java/org/apache/commons/crypto/cipher/JceCipher.java @@ -196,6 +196,62 @@ public int doFinal(byte[] input, int inputOffset, int inputLen, outputOffset); } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the cipher when operating in + * modes such as AEAD (GCM/CCM). If this cipher is operating in + * either GCM or CCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this cipher is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM or CCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if JCE's implementation does not + * support such operation + */ + @Override + public void updateAAD(byte[] aad) { + cipher.updateAAD(aad); + } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the cipher when operating in
+ * modes such as AEAD (GCM/CCM). If this cipher is operating in
+ * either GCM or CCM mode, all AAD must be supplied before beginning
+ * operations on the ciphertext (via the {@code update} and
+ * {@code doFinal} methods).
+ *
+ * @param aad the buffer containing the Additional Authentication Data
+ *
+ * @throws IllegalArgumentException if the {@code aad}
+ * byte array is null
+ * @throws IllegalStateException if this cipher is in a wrong state
+ * (e.g., has not been initialized), does not accept AAD, or if
+ * operating in either GCM or CCM mode and one of the {@code update}
+ * methods has already been called for the active
+ * encryption/decryption operation
+ * @throws UnsupportedOperationException if JCE's implementation does not
+ * support such operation
+ */
+ @Override
+ public void updateAAD(ByteBuffer aad) {
+ cipher.updateAAD(aad);
+ }
+
+
/**
* Closes Jce cipher.
*/
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
index 1820b4779..e7c061a3c 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSsl.java
@@ -18,7 +18,9 @@
package org.apache.commons.crypto.cipher;
import java.nio.ByteBuffer;
+import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
import java.util.StringTokenizer;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
@@ -34,13 +36,15 @@
*/
final class OpenSsl {
+ OpenSslFeedbackCipher opensslBlockCipher;
+
// Mode constant defined by OpenSsl JNI
public static final int ENCRYPT_MODE = 1;
public static final int DECRYPT_MODE = 0;
/** Currently only support AES/CTR/NoPadding. */
private static enum AlgorithmMode {
- AES_CTR, AES_CBC;
+ AES_CTR, AES_CBC, AES_GCM;
/**
* Gets the mode.
@@ -82,10 +86,6 @@ static int get(String padding) throws NoSuchPaddingException {
}
}
- private long context = 0;
- private final int algorithm;
- private final int padding;
-
private static final Throwable loadingFailureReason;
static {
@@ -122,9 +122,11 @@ public static Throwable getLoadingFailureReason() {
* @param padding the padding.
*/
private OpenSsl(long context, int algorithm, int padding) {
- this.context = context;
- this.algorithm = algorithm;
- this.padding = padding;
+ if (algorithm == AlgorithmMode.AES_GCM.ordinal()) {
+ opensslBlockCipher = new OpenSslGaloisCounterMode(context, algorithm, padding);
+ } else {
+ opensslBlockCipher = new OpenSslCommonMode(context, algorithm, padding);
+ }
}
/**
@@ -210,11 +212,13 @@ private static Transform tokenizeTransformation(String transformation)
*
* @param mode {@link #ENCRYPT_MODE} or {@link #DECRYPT_MODE}
* @param key crypto key
- * @param iv crypto iv
+ * @param params the algorithm parameters
+ * @throws InvalidAlgorithmParameterException if IV length is wrong
*/
- public void init(int mode, byte[] key, byte[] iv) {
- context = OpenSslNative
- .init(context, mode, algorithm, padding, key, iv);
+ public void init(int mode, byte[] key, AlgorithmParameterSpec params)
+ throws InvalidAlgorithmParameterException {
+ checkState();
+ opensslBlockCipher.init(mode, key, params);
}
/**
@@ -250,12 +254,7 @@ public int update(ByteBuffer input, ByteBuffer output)
checkState();
Utils.checkArgument(input.isDirect() && output.isDirect(),
"Direct buffers are required.");
- int len = OpenSslNative.update(context, input, input.position(),
- input.remaining(), output, output.position(),
- output.remaining());
- input.position(input.limit());
- output.position(output.position() + len);
- return len;
+ return opensslBlockCipher.update(input, output);
}
/**
@@ -274,8 +273,37 @@ public int update(ByteBuffer input, ByteBuffer output)
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) throws ShortBufferException {
checkState();
- return OpenSslNative.updateByteArray(context, input, inputOffset,
- inputLen, output, outputOffset, output.length - outputOffset);
+ return opensslBlockCipher.update(input, inputOffset, inputLen, output, outputOffset);
+ }
+
+ /**
+ * Encrypts or decrypts data in a single-part operation, or finishes a
+ * multiple-part operation.
+ *
+ * @param input the input byte array
+ * @param inputOffset the offset in input where the input starts
+ * @param inputLen the input length
+ * @param output the byte array for the result
+ * @param outputOffset the offset in output where the result is stored
+ * @return the number of bytes stored in output
+ * @throws ShortBufferException if the given output byte array is too small
+ * to hold the result
+ * @throws BadPaddingException if this cipher is in decryption mode, and
+ * (un)padding has been requested, but the decrypted data is not
+ * bounded by the appropriate padding bytes
+ * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+ * padding has been requested (only in encryption mode), and the
+ * total input length of the data processed by this cipher is not a
+ * multiple of block size; or if this encryption algorithm is unable
+ * to process the input data provided.
+ */
+ public int doFinal(byte[] input, int inputOffset, int inputLen,
+ byte[] output, int outputOffset)
+ throws ShortBufferException, IllegalBlockSizeException,
+ BadPaddingException{
+
+ checkState();
+ return opensslBlockCipher.doFinal(input, inputOffset, inputLen, output, outputOffset);
}
/**
@@ -304,6 +332,7 @@ public int update(byte[] input, int inputOffset, int inputLen,
* If any exception is thrown, this cipher object need to be reset before it
* can be used again.
*
+ * @param input the input ByteBuffer
* @param output the output ByteBuffer
* @return int number of bytes stored in output
* @throws ShortBufferException if the given output byte array is too small
@@ -317,53 +346,43 @@ public int update(byte[] input, int inputOffset, int inputLen,
* (un)padding has been requested, but the decrypted data is not
* bounded by the appropriate padding bytes
*/
- public int doFinal(ByteBuffer output) throws ShortBufferException,
+ public int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
checkState();
Utils.checkArgument(output.isDirect(), "Direct buffer is required.");
- int len = OpenSslNative.doFinal(context, output, output.position(),
- output.remaining());
- output.position(output.position() + len);
- return len;
+
+ return opensslBlockCipher.doFinal(input, output);
}
+
/**
- * Encrypts or decrypts data in a single-part operation, or finishes a
- * multiple-part operation.
+ * Continues a multi-part update of the Additional Authentication
+ * Data (AAD).
+ *
+ * Calls to this method provide AAD to the cipher when operating in
+ * modes such as AEAD (GCM). If this cipher is operating in
+ * either GCM mode, all AAD must be supplied before beginning
+ * operations on the ciphertext (via the {@code update} and
+ * {@code doFinal} methods).
+ *
+ * @param aad the buffer containing the Additional Authentication Data
*
- * @param output the byte array for the result
- * @param outputOffset the offset in output where the result is stored
- * @return the number of bytes stored in output
- * @throws ShortBufferException if the given output byte array is too small
- * to hold the result
- * @throws BadPaddingException if this cipher is in decryption mode, and
- * (un)padding has been requested, but the decrypted data is not
- * bounded by the appropriate padding bytes
- * @throws IllegalBlockSizeException if this cipher is a block cipher, no
- * padding has been requested (only in encryption mode), and the
- * total input length of the data processed by this cipher is not a
- * multiple of block size; or if this encryption algorithm is unable
- * to process the input data provided.
*/
- public int doFinal(byte[] output, int outputOffset)
- throws ShortBufferException, IllegalBlockSizeException,
- BadPaddingException {
- checkState();
- return OpenSslNative.doFinalByteArray(context, output, outputOffset,
- output.length - outputOffset);
+ public void updateAAD(byte[] aad) {
+ this.opensslBlockCipher.updateAAD(aad);
}
+
/** Forcibly clean the context. */
public void clean() {
- if (context != 0) {
- OpenSslNative.clean(context);
- context = 0;
+ if (opensslBlockCipher != null) {
+ opensslBlockCipher.clean();
}
}
/** Checks whether context is initialized. */
private void checkState() {
- Utils.checkState(context != 0);
+ Utils.checkState(opensslBlockCipher != null);
}
@Override
diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
index dff7dad75..c62d58be6 100644
--- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
+++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCipher.java
@@ -37,16 +37,17 @@
*/
class OpenSslCipher implements CryptoCipher {
- private final OpenSsl cipher;
+ private final OpenSsl openSslEngine;
+ private boolean initialized = false;
private final String transformation;
/**
* Constructs a {@link CryptoCipher} using JNI into OpenSSL
*
- * @param props properties for OpenSSL cipher (unused)
- * @param transformation transformation for OpenSSL cipher (algorithm/mode/padding)
- * @throws GeneralSecurityException if OpenSSL cipher initialize failed
+ * @param props properties for OpenSSL openSslEngine (unused)
+ * @param transformation transformation for OpenSSL openSslEngine (algorithm/mode/padding)
+ * @throws GeneralSecurityException if OpenSSL openSslEngine initialize failed
*/
// N.B. this class is not public/protected so does not appear in the main Javadoc
// Please ensure that property use is documented in the enum CryptoRandomFactory.RandomProvider
@@ -59,14 +60,14 @@ public OpenSslCipher(Properties props, String transformation) // NOPMD
throw new RuntimeException(loadingFailureReason);
}
- cipher = OpenSsl.getInstance(transformation);
+ openSslEngine = OpenSsl.getInstance(transformation);
}
/**
* Returns the block size (in bytes).
*
* @return the block size (in bytes), or 0 if the underlying algorithm is
- * not a block cipher
+ * not a block openSslEngine
*/
@Override
public final int getBlockSize() {
@@ -88,10 +89,10 @@ public String getAlgorithm() {
}
/**
- * Initializes the cipher with mode, key and iv.
+ * Initializes the openSslEngine with mode, key and iv.
*
* @param mode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
- * @param key crypto key for the cipher
+ * @param key crypto key for the openSslEngine
* @param params the algorithm parameters
* @throws InvalidKeyException If key length is invalid
* @throws InvalidAlgorithmParameterException if IV length is wrong
@@ -106,20 +107,13 @@ public void init(int mode, Key key, AlgorithmParameterSpec params)
if (mode == Cipher.ENCRYPT_MODE) {
cipherMode = OpenSsl.ENCRYPT_MODE;
}
- byte[] iv;
- if (params instanceof IvParameterSpec) {
- iv = ((IvParameterSpec) params).getIV();
- } else {
- // other AlgorithmParameterSpec such as GCMParameterSpec is not
- // supported now.
- throw new InvalidAlgorithmParameterException("Illegal parameters");
- }
- cipher.init(cipherMode, key.getEncoded(), iv);
+ openSslEngine.init(cipherMode, key.getEncoded(), params);
+ initialized = true;
}
/**
* Continues a multiple-part encryption/decryption operation. The data is
- * encrypted or decrypted, depending on how this cipher was initialized.
+ * encrypted or decrypted, depending on how this openSslEngine was initialized.
*
* @param inBuffer the input ByteBuffer
* @param outBuffer the output ByteBuffer
@@ -130,12 +124,12 @@ public void init(int mode, Key key, AlgorithmParameterSpec params)
@Override
public int update(ByteBuffer inBuffer, ByteBuffer outBuffer)
throws ShortBufferException {
- return cipher.update(inBuffer, outBuffer);
+ return openSslEngine.update(inBuffer, outBuffer);
}
/**
* Continues a multiple-part encryption/decryption operation. The data is
- * encrypted or decrypted, depending on how this cipher was initialized.
+ * encrypted or decrypted, depending on how this openSslEngine was initialized.
*
* @param input the input byte array
* @param inputOffset the offset in input where the input starts
@@ -149,24 +143,24 @@ public int update(ByteBuffer inBuffer, ByteBuffer outBuffer)
@Override
public int update(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) throws ShortBufferException {
- return cipher
+ return openSslEngine
.update(input, inputOffset, inputLen, output, outputOffset);
}
/**
* Encrypts or decrypts data in a single-part operation, or finishes a
* multiple-part operation. The data is encrypted or decrypted, depending on
- * how this cipher was initialized.
+ * how this openSslEngine was initialized.
*
* @param inBuffer the input ByteBuffer
* @param outBuffer the output ByteBuffer
* @return int number of bytes stored in output
- * @throws BadPaddingException if this cipher is in decryption mode, and
+ * @throws BadPaddingException if this openSslEngine is in decryption mode, and
* (un)padding has been requested, but the decrypted data is not
* bounded by the appropriate padding bytes
- * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+ * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
* padding has been requested (only in encryption mode), and the
- * total input length of the data processed by this cipher is not a
+ * total input length of the data processed by this openSslEngine is not a
* multiple of block size; or if this encryption algorithm is unable
* to process the input data provided.
* @throws ShortBufferException if the given output buffer is too small to
@@ -176,8 +170,7 @@ public int update(byte[] input, int inputOffset, int inputLen,
public int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
- int n = cipher.update(inBuffer, outBuffer);
- return n + cipher.doFinal(outBuffer);
+ return openSslEngine.doFinal(inBuffer, outBuffer);
}
/**
@@ -192,12 +185,12 @@ public int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
* @return the number of bytes stored in output
* @throws ShortBufferException if the given output byte array is too small
* to hold the result
- * @throws BadPaddingException if this cipher is in decryption mode, and
+ * @throws BadPaddingException if this openSslEngine is in decryption mode, and
* (un)padding has been requested, but the decrypted data is not
* bounded by the appropriate padding bytes
- * @throws IllegalBlockSizeException if this cipher is a block cipher, no
+ * @throws IllegalBlockSizeException if this openSslEngine is a block openSslEngine, no
* padding has been requested (only in encryption mode), and the
- * total input length of the data processed by this cipher is not a
+ * total input length of the data processed by this openSslEngine is not a
* multiple of block size; or if this encryption algorithm is unable
* to process the input data provided.
*/
@@ -205,16 +198,95 @@ public int doFinal(ByteBuffer inBuffer, ByteBuffer outBuffer)
public int doFinal(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
- int n = cipher.update(input, inputOffset, inputLen, output,
- outputOffset);
- return n + cipher.doFinal(output, outputOffset + n);
+ return openSslEngine.doFinal(input, inputOffset, inputLen, output,outputOffset);
+ }
+
+
+ /**
+ * Continues a multi-part update of the Additional Authentication
+ * Data (AAD).
+ *
+ * Calls to this method provide AAD to the opensslEngine when operating in + * modes such as AEAD (GCM). If this opensslEngine is operating in + * either GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this opensslEngine is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the implementation {@code opensslEngine} + * doesn't support this operation. + */ + @Override + public void updateAAD(byte[] aad) throws IllegalArgumentException, + IllegalStateException, UnsupportedOperationException { + if (aad == null) { + throw new IllegalArgumentException("aad buffer is null"); + } + if (!initialized) { + throw new IllegalStateException("Cipher not initialized"); + } + if (aad.length == 0) { + return; + } + + openSslEngine.updateAAD(aad); } /** - * Closes the OpenSSL cipher. Clean the OpenSsl native context. + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the opensslEngine when operating in + * modes such as AEAD (GCM). If this opensslEngine is operating in + * either GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this opensslEngine is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the implementation {@code opensslEngine} + * doesn't support this operation. + */ + @Override + public void updateAAD(ByteBuffer aad) throws IllegalArgumentException, + IllegalStateException, UnsupportedOperationException { + if (aad == null) { + throw new IllegalArgumentException("aad buffer is null"); + } + if (!initialized) { + throw new IllegalStateException("Cipher not initialized"); + } + + int aadLen = aad.limit() - aad.position(); + if (aadLen == 0) { + return; + } + byte[] aadBytes = new byte[aadLen]; + aad.get(aadBytes); + openSslEngine.updateAAD(aadBytes); + } + + + /** + * Closes the OpenSSL openSslEngine. Clean the OpenSsl native context. */ @Override public void close() { - cipher.clean(); + openSslEngine.clean(); } } diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java new file mode 100644 index 000000000..f602d1145 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslCommonMode.java @@ -0,0 +1,117 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.crypto.cipher; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; + +/** + * This class do the real work(Encryption/Decryption) for non-authenticated modes, such as CTR, CBC. + *
+ * It will call the OpenSSL API to implement encryption/decryption + */ +class OpenSslCommonMode extends OpenSslFeedbackCipher { + + public OpenSslCommonMode(long context, int algorithmMode, int padding) { + super(context, algorithmMode, padding); + } + + @Override + public void init(int mode, byte[] key, AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + this.cipherMode = mode; + byte[] iv; + if (params instanceof IvParameterSpec) { + iv = ((IvParameterSpec) params).getIV(); + } else { + // other AlgorithmParameterSpec is not supported now. + throw new InvalidAlgorithmParameterException("Illegal parameters"); + } + context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); + } + + @Override + public int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException { + checkState(); + + int len = OpenSslNative.update(context, input, input.position(), + input.remaining(), output, output.position(), + output.remaining()); + input.position(input.limit()); + output.position(output.position() + len); + + return len; + } + + @Override + public int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException { + checkState(); + + return OpenSslNative.updateByteArray(context, input, inputOffset, + inputLen, output, outputOffset, output.length - outputOffset); + } + + @Override + public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + checkState(); + + int len = OpenSslNative.updateByteArray(context, input, inputOffset, + inputLen, output, outputOffset, output.length - outputOffset); + + len += OpenSslNative.doFinalByteArray(context, output, outputOffset + len, + output.length - outputOffset - len); + + return len; + } + + @Override + public int doFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + checkState(); + + int totalLen = 0; + int len = OpenSslNative.update(context, input, input.position(), + input.remaining(), output, output.position(), output.remaining()); + totalLen += len; + + input.position(input.limit()); + output.position(output.position() + len); + + len = OpenSslNative.doFinal(context, output, output.position(), + output.remaining()); + totalLen += len; + + output.position(output.position() + len); + + return totalLen; + } + + @Override + public void updateAAD(byte[] aad) { + throw new UnsupportedOperationException( + "The underlying Cipher implementation " + + "does not support this method"); + } +} diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java new file mode 100644 index 000000000..2b64ff546 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslEvpCtrlValues.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.crypto.cipher; + +/** + * This enum is defined for OpensslNative.ctrl() to allow various cipher + * specific parameters to be determined and set. + * see the macro definitions in openssl/evp.h + */ +enum OpenSslEvpCtrlValues { + INIT(0x00), + SET_KEY_LENGTH(0x01), + GET_RC2_KEY_BITS(0x02), + SET_RC2_KEY_BITS(0x03), + GET_RC5_ROUNDS(0x04), + SET_RC5_ROUNDS(0x05), + RAND_KEY(0x06), + PBE_PRF_NID(0x07), + COPY(0x08), + AEAD_SET_IVLEN(0x09), + AEAD_GET_TAG(0x10), + AEAD_SET_TAG(0x11), + AEAD_SET_IV_FIXED(0x12), + GCM_IV_GEN(0x13), + CCM_SET_L(0x14), + CCM_SET_MSGLEN(0x15); + + private final int value; + + OpenSslEvpCtrlValues(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java new file mode 100644 index 000000000..4fa2743c8 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslFeedbackCipher.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.crypto.cipher; + +import org.apache.commons.crypto.utils.Utils; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; + +/** + * This class represents a block cipher in one of its modes. + */ +abstract class OpenSslFeedbackCipher { + + protected long context = 0; + protected int algorithmMode; + protected int padding; + + protected int cipherMode = OpenSsl.DECRYPT_MODE; + + OpenSslFeedbackCipher(long context, int algorithmMode, int padding) { + this.context = context; + this.algorithmMode = algorithmMode; + this.padding = padding; + } + + abstract void init(int mode, byte[] key, AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException; + + abstract int update(ByteBuffer input, ByteBuffer output) + throws ShortBufferException; + + abstract int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException; + + abstract int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException; + + abstract int doFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException; + + abstract void updateAAD(byte[] aad); + + public void clean() { + if (context != 0) { + OpenSslNative.clean(context); + context = 0; + } + } + + public void checkState() { + Utils.checkState(context != 0); + } +} diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java new file mode 100644 index 000000000..991c475a1 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslGaloisCounterMode.java @@ -0,0 +1,313 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.crypto.cipher; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.InvalidAlgorithmParameterException; +import java.security.spec.AlgorithmParameterSpec; + +/** + * This class do the real work(Encryption/Decryption/Authentication) for the authenticated mode: GCM. + * + * It calls the OpenSSL API to implement the JCE-like behavior + * + * @since 1.1 + */ +class OpenSslGaloisCounterMode extends OpenSslFeedbackCipher { + + // buffer for AAD data; if consumed, set as null + private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); + private int tagBitLen = -1; + + static final int DEFAULT_TAG_LEN = 16; + + // buffer for storing input in decryption, not used for encryption + private ByteArrayOutputStream inBuffer = null; + + public OpenSslGaloisCounterMode(long context, int algorithmMode, int padding) { + super(context, algorithmMode, padding); + } + + @Override + public void init(int mode, byte[] key, AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + + if (aadBuffer == null) { + aadBuffer = new ByteArrayOutputStream(); + } else { + aadBuffer.reset(); + } + + this.cipherMode = mode; + byte[] iv; + if (params instanceof GCMParameterSpec) { + GCMParameterSpec gcmParam = (GCMParameterSpec) params; + iv = gcmParam.getIV(); + this.tagBitLen = gcmParam.getTLen(); + } else { + // other AlgorithmParameterSpec is not supported now. + throw new InvalidAlgorithmParameterException("Illegal parameters"); + } + + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + inBuffer = new ByteArrayOutputStream(); + } + + context = OpenSslNative.init(context, mode, algorithmMode, padding, key, iv); + } + + @Override + public int update(ByteBuffer input, ByteBuffer output) throws ShortBufferException { + checkState(); + + processAAD(); + + int len; + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + // store internally until doFinal(decrypt) is called because + // spec mentioned that only return recovered data after tag + // is successfully verified + int inputLen = input.remaining(); + byte[] inputBuf = new byte[inputLen]; + input.get(inputBuf, 0, inputLen); + inBuffer.write(inputBuf, 0, inputLen); + return 0; + } else { + len = OpenSslNative.update(context, input, input.position(), + input.remaining(), output, output.position(), + output.remaining()); + input.position(input.limit()); + output.position(output.position() + len); + } + + return len; + } + + @Override + public int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException { + checkState(); + + processAAD(); + + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + // store internally until doFinal(decrypt) is called because + // spec mentioned that only return recovered data after tag + // is successfully verified + inBuffer.write(input, inputOffset, inputLen); + return 0; + } else { + return OpenSslNative.updateByteArray(context, input, inputOffset, + inputLen, output, outputOffset, output.length - outputOffset); + } + } + + @Override + public int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + checkState(); + + processAAD(); + + int len; + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + // if GCM-DECRYPT, we have to handle the buffered input + // and the retrieve the trailing tag from input + int inputOffsetFinal = inputOffset; + int inputLenFinal = inputLen; + byte[] inputFinal; + if (inBuffer != null && inBuffer.size() > 0) { + inBuffer.write(input, inputOffset, inputLen); + inputFinal = inBuffer.toByteArray(); + inputOffsetFinal = 0; + inputLenFinal = inputFinal.length; + inBuffer.reset(); + } else { + inputFinal = input; + } + + if (inputFinal.length < getTagLen()) { + throw new AEADBadTagException("Input too short - need tag"); + } + + int inputDataLen = inputLenFinal - getTagLen(); + len = OpenSslNative.updateByteArray(context, inputFinal, inputOffsetFinal, + inputDataLen, output, outputOffset, output.length - outputOffset); + + // set tag to EVP_Cipher for integrity verification in doFinal + ByteBuffer tag = ByteBuffer.allocate(getTagLen()); + tag.put(input, input.length - getTagLen(), getTagLen()); + tag.flip(); + evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(), getTagLen(), tag); + } else { + len = OpenSslNative.updateByteArray(context, input, inputOffset, + inputLen, output, outputOffset, output.length - outputOffset); + } + + len += OpenSslNative.doFinalByteArray(context, output, outputOffset + len, + output.length - outputOffset - len); + + // Keep the similar behavior as JCE, append the tag to end of output + if (this.cipherMode == OpenSsl.ENCRYPT_MODE) { + ByteBuffer tag; + tag = ByteBuffer.allocate(getTagLen()); + evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag); + tag.get(output, output.length - getTagLen(), getTagLen()); + len += getTagLen(); + } + + return len; + } + + @Override + public int doFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + checkState(); + + processAAD(); + + int totalLen = 0; + int len; + if (this.cipherMode == OpenSsl.DECRYPT_MODE) { + ByteBuffer tag = ByteBuffer.allocate(getTagLen()); + + // if GCM-DECRYPT, we have to handle the buffered input + // and the retrieve the trailing tag from input + if (inBuffer != null && inBuffer.size() > 0) { + byte[] inputBytes = new byte[input.remaining()]; + input.get(inputBytes, 0, inputBytes.length); + inBuffer.write(inputBytes, 0, inputBytes.length); + byte[] inputFinal = inBuffer.toByteArray(); + inBuffer.reset(); + + if (inputFinal.length < getTagLen()) { + throw new AEADBadTagException("Input too short - need tag"); + } + + len = OpenSslNative.updateByteArrayByteBuffer(context, inputFinal, 0, + inputFinal.length - getTagLen(), + output, output.position(), output.remaining()); + + // retrieve tag + tag.put(inputFinal, inputFinal.length - getTagLen(), getTagLen()); + tag.flip(); + + } else { + // if no buffered input, just use the input directly + if (input.remaining() < getTagLen()) { + throw new AEADBadTagException("Input too short - need tag"); + } + + len = OpenSslNative.update(context, input, input.position(), + input.remaining() - getTagLen(), output, output.position(), + output.remaining()); + + input.position(input.position() + len); + + // retrieve tag + tag.put(input); + tag.flip(); + } + + // set tag to EVP_Cipher for integrity verification in doFinal + evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_SET_TAG.getValue(), + getTagLen(), tag); + } else { + len = OpenSslNative.update(context, input, input.position(), + input.remaining(), output, output.position(), + output.remaining()); + input.position(input.limit()); + } + + totalLen += len; + output.position(output.position() + len); + + len = OpenSslNative.doFinal(context, output, output.position(), + output.remaining()); + output.position(output.position() + len); + totalLen += len; + + // Keep the similar behavior as JCE, append the tag to end of output + if (this.cipherMode == OpenSsl.ENCRYPT_MODE) { + ByteBuffer tag; + tag = ByteBuffer.allocate(getTagLen()); + evpCipherCtxCtrl(context, OpenSslEvpCtrlValues.AEAD_GET_TAG.getValue(), getTagLen(), tag); + output.put(tag); + totalLen += getTagLen(); + } + + return totalLen; + } + + public void clean() { + super.clean(); + aadBuffer = null; + } + + @Override + public void updateAAD(byte[] aad) { + // must be called after initialized. + if (aadBuffer != null) { + aadBuffer.write(aad, 0, aad.length); + } else { + // update has already been called + throw new IllegalStateException + ("Update has been called; no more AAD data"); + } + } + + private void processAAD() { + if (aadBuffer != null && aadBuffer.size() > 0) { + OpenSslNative.updateByteArray(context, aadBuffer.toByteArray(), + 0, aadBuffer.size(), null, 0, 0); + aadBuffer = null; + } + } + + private int getTagLen() { + return tagBitLen < 0 ? DEFAULT_TAG_LEN : (tagBitLen >> 3); + } + + /** + * a wrapper of OpenSslNative.ctrl(long context, int type, int arg, byte[] data) + * Since native interface EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) is generic, + * it may set/get any native char or long type to the data buffer(ptr). + * Here we use ByteBuffer and set nativeOrder to handle the endianness. + */ + private void evpCipherCtxCtrl(long context, int type, int arg, ByteBuffer bb) { + checkState(); + + try { + if (bb != null) { + bb.order(ByteOrder.nativeOrder()); + OpenSslNative.ctrl(context, type, arg, bb.array()); + } else { + OpenSslNative.ctrl(context, type, arg, null); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java index c73be17cf..dcaf37a0d 100644 --- a/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java +++ b/src/main/java/org/apache/commons/crypto/cipher/OpenSslNative.java @@ -6,9 +6,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *
+ * http://www.apache.org/licenses/LICENSE-2.0 + *
* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,9 +25,9 @@ */ class OpenSslNative { - /** - * The private constructor of {@link OpenSslNative}. - */ + /** + * The private constructor of {@link OpenSslNative}. + */ private OpenSslNative() { } @@ -57,7 +57,7 @@ private OpenSslNative() { * @return the context address of cipher */ public native static long init(long context, int mode, int alg, - int padding, byte[] key, byte[] iv); + int padding, byte[] key, byte[] iv); /** * Continues a multiple-part encryption/decryption operation. The data is @@ -93,6 +93,23 @@ public native static int updateByteArray(long context, byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength); + /** + * Continues a multiple-part encryption/decryption operation. The data is + * encrypted or decrypted, depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param input The input byte array + * @param inputOffset The offset in input where the input starts + * @param inputLength The input length + * @param output The byte buffer for the result + * @param outputOffset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public native static int updateByteArrayByteBuffer(long context, byte[] input, + int inputOffset, int inputLength, + ByteBuffer output, int outputOffset, int maxOutputLength); + /** * Finishes a multiple-part operation. The data is encrypted or decrypted, * depending on how this cipher was initialized. @@ -119,6 +136,23 @@ public native static int doFinal(long context, ByteBuffer output, public native static int doFinalByteArray(long context, byte[] output, int offset, int maxOutputLength); + /** + * allows various cipher specific parameters to be determined and set. + * + * it will call OpenSSL's API + * int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr) + * In OpenSSL, data type of ptr can be char* or long*. Here, we map java's + * byte[] to native void*ptr. Note that the byte order is ByteOrder.nativeOrder. + * + * @param context The cipher context address + * @param type CtrlValues + * @param arg + * @param data byte buffer or null + * @return return 0 if there is any error, else return 1. + */ + public native static int ctrl(long context, int type, int arg, byte[] data); + + /** * Cleans the context at native. * diff --git a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java index c83f1505e..01de55447 100644 --- a/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java +++ b/src/main/java/org/apache/commons/crypto/jna/OpenSslJnaCipher.java @@ -232,6 +232,68 @@ public int doFinal(byte[] input, int inputOffset, int inputLen, return doFinal(inputBuf, outputBuf); } + + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the opensslEngine when operating in + * modes such as AEAD (GCM). If this opensslEngine is operating in + * either GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this opensslEngine is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the implementation {@code opensslEngine} + * doesn't support this operation. + */ + @Override + public void updateAAD(byte[] aad) throws IllegalArgumentException, + IllegalStateException, UnsupportedOperationException { + //TODO: implement GCM mode using Jna + throw new UnsupportedOperationException("This is unsupported in Jna Cipher"); + } + + /** + * Continues a multi-part update of the Additional Authentication + * Data (AAD). + *
+ * Calls to this method provide AAD to the opensslEngine when operating in + * modes such as AEAD (GCM). If this opensslEngine is operating in + * either GCM mode, all AAD must be supplied before beginning + * operations on the ciphertext (via the {@code update} and + * {@code doFinal} methods). + * + * @param aad the buffer containing the Additional Authentication Data + * + * @throws IllegalArgumentException if the {@code aad} + * byte array is null + * @throws IllegalStateException if this opensslEngine is in a wrong state + * (e.g., has not been initialized), does not accept AAD, or if + * operating in either GCM mode and one of the {@code update} + * methods has already been called for the active + * encryption/decryption operation + * @throws UnsupportedOperationException if the implementation {@code opensslEngine} + * doesn't support this operation. + */ + @Override + public void updateAAD(ByteBuffer aad) throws IllegalArgumentException, + IllegalStateException, UnsupportedOperationException { + //TODO: implement GCM mode using Jna + throw new UnsupportedOperationException("This is unsupported in Jna Cipher"); + } + + + /** * Closes the OpenSSL cipher. Clean the OpenSsl native context. */ diff --git a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c index 46196e509..078e2f2c8 100644 --- a/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c +++ b/src/main/native/org/apache/commons/crypto/cipher/OpenSslNative.c @@ -35,6 +35,7 @@ static void (*dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *); static int (*dlsym_EVP_CIPHER_CTX_cleanup)(EVP_CIPHER_CTX *); static void (*dlsym_EVP_CIPHER_CTX_init)(EVP_CIPHER_CTX *); static int (*dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, int); +static int (*dlsym_EVP_CIPHER_CTX_ctrl)(EVP_CIPHER_CTX *, int, int, void *); static int (*dlsym_EVP_CipherInit_ex)(EVP_CIPHER_CTX *, const EVP_CIPHER *, \ ENGINE *, const unsigned char *, const unsigned char *, int); static int (*dlsym_EVP_CipherUpdate)(EVP_CIPHER_CTX *, unsigned char *, \ @@ -46,6 +47,9 @@ static EVP_CIPHER * (*dlsym_EVP_aes_128_ctr)(void); static EVP_CIPHER * (*dlsym_EVP_aes_256_cbc)(void); static EVP_CIPHER * (*dlsym_EVP_aes_192_cbc)(void); static EVP_CIPHER * (*dlsym_EVP_aes_128_cbc)(void); +static EVP_CIPHER * (*dlsym_EVP_aes_256_gcm)(void); +static EVP_CIPHER * (*dlsym_EVP_aes_192_gcm)(void); +static EVP_CIPHER * (*dlsym_EVP_aes_128_gcm)(void); #endif #ifdef WINDOWS @@ -54,6 +58,7 @@ typedef void (__cdecl *__dlsym_EVP_CIPHER_CTX_free)(EVP_CIPHER_CTX *); typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_cleanup)(EVP_CIPHER_CTX *); typedef void (__cdecl *__dlsym_EVP_CIPHER_CTX_init)(EVP_CIPHER_CTX *); typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_set_padding)(EVP_CIPHER_CTX *, int); +typedef int (__cdecl *__dlsym_EVP_CIPHER_CTX_ctrl)(EVP_CIPHER_CTX *, int, int, void *); typedef int (__cdecl *__dlsym_EVP_CipherInit_ex)(EVP_CIPHER_CTX *, \ const EVP_CIPHER *, ENGINE *, const unsigned char *, \ const unsigned char *, int); @@ -67,11 +72,15 @@ typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_128_ctr)(void); typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_256_cbc)(void); typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_192_cbc)(void); typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_128_cbc)(void); +typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_256_gcm)(void); +typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_192_gcm)(void); +typedef EVP_CIPHER * (__cdecl *__dlsym_EVP_aes_128_gcm)(void); static __dlsym_EVP_CIPHER_CTX_new dlsym_EVP_CIPHER_CTX_new; static __dlsym_EVP_CIPHER_CTX_free dlsym_EVP_CIPHER_CTX_free; static __dlsym_EVP_CIPHER_CTX_cleanup dlsym_EVP_CIPHER_CTX_cleanup; static __dlsym_EVP_CIPHER_CTX_init dlsym_EVP_CIPHER_CTX_init; static __dlsym_EVP_CIPHER_CTX_set_padding dlsym_EVP_CIPHER_CTX_set_padding; +static __dlsym_EVP_CIPHER_CTX_ctrl dlsym_EVP_CIPHER_CTX_ctrl; static __dlsym_EVP_CipherInit_ex dlsym_EVP_CipherInit_ex; static __dlsym_EVP_CipherUpdate dlsym_EVP_CipherUpdate; static __dlsym_EVP_CipherFinal_ex dlsym_EVP_CipherFinal_ex; @@ -81,6 +90,9 @@ static __dlsym_EVP_aes_128_ctr dlsym_EVP_aes_128_ctr; static __dlsym_EVP_aes_256_cbc dlsym_EVP_aes_256_cbc; static __dlsym_EVP_aes_192_cbc dlsym_EVP_aes_192_cbc; static __dlsym_EVP_aes_128_cbc dlsym_EVP_aes_128_cbc; +static __dlsym_EVP_aes_256_gcm dlsym_EVP_aes_256_gcm; +static __dlsym_EVP_aes_192_gcm dlsym_EVP_aes_192_gcm; +static __dlsym_EVP_aes_128_gcm dlsym_EVP_aes_128_gcm; #endif #ifdef UNIX @@ -98,6 +110,9 @@ static void loadAes(JNIEnv *env, HMODULE openssl) LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_cbc, env, openssl, "EVP_aes_256_cbc"); LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_cbc, env, openssl, "EVP_aes_192_cbc"); LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_cbc, env, openssl, "EVP_aes_128_cbc"); + LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_256_gcm, env, openssl, "EVP_aes_256_gcm"); + LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_192_gcm, env, openssl, "EVP_aes_192_gcm"); + LOAD_DYNAMIC_SYMBOL(dlsym_EVP_aes_128_gcm, env, openssl, "EVP_aes_128_gcm"); #endif #ifdef WINDOWS @@ -113,6 +128,12 @@ static void loadAes(JNIEnv *env, HMODULE openssl) env, openssl, "EVP_aes_192_cbc"); LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_128_cbc, dlsym_EVP_aes_128_cbc, \ env, openssl, "EVP_aes_128_cbc"); + LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_256_gcm, dlsym_EVP_aes_256_gcm, \ + env, openssl, "EVP_aes_256_gcm"); + LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_192_gcm, dlsym_EVP_aes_192_gcm, \ + env, openssl, "EVP_aes_192_gcm"); + LOAD_DYNAMIC_SYMBOL(__dlsym_EVP_aes_128_gcm, dlsym_EVP_aes_128_gcm, \ + env, openssl, "EVP_aes_128_gcm"); #endif } @@ -153,6 +174,8 @@ JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initI "EVP_CIPHER_CTX_init"); LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_set_padding, env, openssl, \ "EVP_CIPHER_CTX_set_padding"); + LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CIPHER_CTX_ctrl, env, openssl, \ + "EVP_CIPHER_CTX_ctrl"); LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherInit_ex, env, openssl, \ "EVP_CipherInit_ex"); LOAD_DYNAMIC_SYMBOL(dlsym_EVP_CipherUpdate, env, openssl, \ @@ -195,12 +218,13 @@ JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initI JNIEXPORT jlong JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_initContext (JNIEnv *env, jclass clazz, jint alg, jint padding) { - if (alg != AES_CTR && alg != AES_CBC) { + if (alg != AES_CTR && alg != AES_CBC && alg != AES_GCM) { THROW(env, "java/security/NoSuchAlgorithmException", NULL); return (jlong)0; } if (!(alg == AES_CTR && padding == NOPADDING) - && !(alg == AES_CBC && (padding == NOPADDING|| padding == PKCS5PADDING))) { + && !(alg == AES_CBC && (padding == NOPADDING|| padding == PKCS5PADDING)) + && !(alg == AES_GCM && padding == NOPADDING)) { THROW(env, "javax/crypto/NoSuchPaddingException", NULL); return (jlong)0; } @@ -219,6 +243,13 @@ JNIEXPORT jlong JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_init return (jlong)0; } + if (dlsym_EVP_aes_256_gcm == NULL || + dlsym_EVP_aes_192_gcm == NULL || dlsym_EVP_aes_128_gcm == NULL) { + THROW(env, "java/security/NoSuchAlgorithmException", \ + "Doesn't support AES GCM."); + return (jlong)0; + } + // Create and initialize a EVP_CIPHER_CTX EVP_CIPHER_CTX *context = dlsym_EVP_CIPHER_CTX_new(); if (!context) { @@ -249,6 +280,14 @@ static EVP_CIPHER * getEvpCipher(int alg, int keyLen) } else if (keyLen == KEY_LENGTH_128) { cipher = dlsym_EVP_aes_128_cbc(); } + } else if (alg == AES_GCM) { + if (keyLen == KEY_LENGTH_256) { + cipher = dlsym_EVP_aes_256_gcm(); + } else if (keyLen == KEY_LENGTH_192) { + cipher = dlsym_EVP_aes_192_gcm(); + } else if (keyLen == KEY_LENGTH_128) { + cipher = dlsym_EVP_aes_128_gcm(); + } } return cipher; } @@ -271,7 +310,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_init THROW(env, "java/security/InvalidKeyException", str); goto cleanup; } - if (jIvLen != IV_LENGTH) { + if ((alg != AES_GCM) && (jIvLen != IV_LENGTH)) { THROW(env, "java/security/InvalidAlgorithmParameterException", "Wrong IV length: must be 16 bytes long"); goto cleanup; } @@ -296,13 +335,26 @@ JNIEXPORT jlong JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_init goto cleanup; } - if (!(alg == AES_CTR || alg == AES_CBC)) { + if (!(alg == AES_CTR || alg == AES_CBC || alg == AES_GCM)) { THROW(env, "java/security/NoSuchAlgorithmException", "The algorithm is not supported."); goto cleanup; } + // initialize cipher & mode int rc = dlsym_EVP_CipherInit_ex(context, getEvpCipher(alg, jKeyLen), \ - NULL, (unsigned char *)jKey, (unsigned char *)jIv, mode == ENCRYPT_MODE); + NULL, NULL, NULL, mode == ENCRYPT_MODE); + if (rc == 0) { + THROW(env, "java/lang/InternalError", "Error in EVP_CipherInit_ex."); + goto cleanup; + } + + // Set IV length if default 12 bytes (96 bits) is not appropriate + // Note: set IV length after cipher is initialized, before iv is initialized. + if (alg == AES_GCM) { + rc = dlsym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, jIvLen, NULL); + } + rc = dlsym_EVP_CipherInit_ex(context, NULL, NULL, \ + (unsigned char *)jKey, (unsigned char *)jIv, mode == ENCRYPT_MODE); if (rc == 0) { THROW(env, "java/lang/InternalError", "Error in EVP_CipherInit_ex."); goto cleanup; @@ -394,20 +446,28 @@ JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_updat jint input_len, jbyteArray output, jint output_offset, jint max_output_len) { EVP_CIPHER_CTX *context = CONTEXT(ctx); - if (!check_update_max_output_len(context, input_len, max_output_len)) { + + // when provide AAD to EVP cipher, output is NULL. + if (output != NULL + && !check_update_max_output_len(context, input_len, max_output_len)) { THROW(env, "javax/crypto/ShortBufferException", \ "Output buffer is not sufficient."); return 0; } + unsigned char *input_bytes = NULL; unsigned char *output_bytes = NULL; int output_len = 0; input_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, input, 0); - output_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, output, 0); - if (input_bytes == NULL || output_bytes == NULL) { + + // output is NULL when updateAAD + if (output != NULL) { + output_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, output, 0); + } + if (input_bytes == NULL || (output != NULL && output_bytes == NULL)) { THROW(env, "java/lang/InternalError", "Cannot get buffer address."); - goto cleanup; + return 0; } int rc = dlsym_EVP_CipherUpdate(context, output_bytes + output_offset, &output_len, \ @@ -429,6 +489,37 @@ JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_updat return output_len; } +JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_updateByteArrayByteBuffer + (JNIEnv *env, jclass clazz, jlong ctx, jbyteArray input, jint input_offset, + jint input_len, jobject output, jint output_offset, jint max_output_len) +{ + EVP_CIPHER_CTX *context = CONTEXT(ctx); + if (!check_update_max_output_len(context, input_len, max_output_len)) { + THROW(env, "javax/crypto/ShortBufferException", \ + "Output buffer is not sufficient."); + return 0; + } + unsigned char *input_bytes = (unsigned char *) (*env)->GetByteArrayElements(env, input, 0); + unsigned char *output_bytes = (*env)->GetDirectBufferAddress(env, output); + if (input_bytes == NULL || output_bytes == NULL) { + THROW(env, "java/lang/InternalError", "Cannot get buffer address."); + return 0; + } + input_bytes = input_bytes + input_offset; + output_bytes = output_bytes + output_offset; + + int output_len = 0; + if (!dlsym_EVP_CipherUpdate(context, output_bytes, &output_len, \ + input_bytes, input_len)) { + (*env)->ReleaseByteArrayElements(env, input, (jbyte *) input_bytes, 0); + dlsym_EVP_CIPHER_CTX_cleanup(context); + THROW(env, "java/lang/InternalError", "Error in EVP_CipherUpdate."); + return 0; + } + return output_len; +} + + // https://www.openssl.org/docs/crypto/EVP_EncryptInit.html static int check_doFinal_max_output_len(EVP_CIPHER_CTX *context, int max_output_len) @@ -464,8 +555,14 @@ JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_doFin int output_len = 0; if (!dlsym_EVP_CipherFinal_ex(context, output_bytes, &output_len)) { + // validate tag in GCM mode when decrypt + if ((context->cipher->flags & EVP_CIPH_MODE) == EVP_CIPH_GCM_MODE + && context->encrypt == DECRYPT_MODE) { + THROW(env, "javax/crypto/AEADBadTagException", "Tag mismatch!"); + } else { + THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex."); + } dlsym_EVP_CIPHER_CTX_cleanup(context); - THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex."); return 0; } return output_len; @@ -493,13 +590,64 @@ JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_doFin (*env)->ReleaseByteArrayElements(env, output, (jbyte *) output_bytes, 0); if (rc == 0) { - dlsym_EVP_CIPHER_CTX_cleanup(context); + // validate tag in GCM mode when decrypt + if ((context->cipher->flags & EVP_CIPH_MODE) == EVP_CIPH_GCM_MODE + && context->encrypt == DECRYPT_MODE) { + THROW(env, "javax/crypto/AEADBadTagException", "Tag mismatch!"); + } else { THROW(env, "java/lang/InternalError", "Error in EVP_CipherFinal_ex."); + } + dlsym_EVP_CIPHER_CTX_cleanup(context); return 0; } return output_len; } +JNIEXPORT jint JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_ctrl + (JNIEnv *env, jclass clazz, jlong ctx, jint type, jint arg, jbyteArray data) +{ + EVP_CIPHER_CTX *context = CONTEXT(ctx); + + int rc = 0; + void *data_ptr = NULL; + if (data != NULL) { + data_ptr = (void*) (*env)->GetByteArrayElements(env, data, 0); + if (data_ptr == NULL) { + THROW(env, "java/lang/InternalError", "Cannot get buffer address."); + return 0; + } + } + + // get/set tag for GCM + if (type == EVP_CTRL_GCM_GET_TAG || type == EVP_CTRL_GCM_SET_TAG) { + if (arg <= 0 || arg > 16) { + THROW(env, "javax/crypto/AEADBadTagException", "TAG_LENGTH_INTERNAL_ERROR"); + goto exit_; + } + if (data == NULL) { + THROW(env, "javax/crypto/AEADBadTagException", "tag is null"); + goto exit_; + } + + unsigned char *tag = (unsigned char*) data_ptr; + rc = dlsym_EVP_CIPHER_CTX_ctrl(context, type, arg, tag); + if (!rc) { + THROW(env, "javax/crypto/AEADBadTagException", "TAG_SET_ERROR or TAG_RETRIEVE_ERROR"); + goto exit_; + } + } else { + THROW(env, "java.lang.UnsupportedOperationException", "Not implemented yet!"); + goto exit_; + } + +exit_: + if (data_ptr != NULL) { + (*env)->ReleaseByteArrayElements(env, data, (jbyte *) data_ptr, 0); + } + return rc; +} + + JNIEXPORT void JNICALL Java_org_apache_commons_crypto_cipher_OpenSslNative_clean (JNIEnv *env, jclass clazz, jlong ctx) { diff --git a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h index 936cfb343..b980b77cc 100644 --- a/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h +++ b/src/main/native/org/apache/commons/crypto/org_apache_commons_crypto.h @@ -216,12 +216,15 @@ static FARPROC WINAPI do_dlsym(JNIEnv *env, HMODULE handle, LPCSTR symbol) { #define ENCRYPT_MODE 1 #define DECRYPT_MODE 0 -/** Currently only support AES/CTR/NoPadding. */ +/** Currently only support AES/CTR/NoPadding, AES/CBC/NoPadding, AES/CBC/PKCS5Padding, AES/GCM/NoPadding */ #define AES_CTR 0 #define AES_CBC 1 +#define AES_GCM 2 + #define NOPADDING 0 #define PKCS5PADDING 1 + #endif //vim: sw=2: ts=2: et diff --git a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java index a4cd2c6a3..4f43a0c37 100644 --- a/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java +++ b/src/test/java/org/apache/commons/crypto/cipher/OpenSslCipherTest.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; import org.junit.Assert; import org.junit.Assume; @@ -89,7 +90,7 @@ public void testUpdateArguments() throws Exception { .getInstance("AES/CTR/NoPadding"); Assert.assertNotNull(cipher); - cipher.init(OpenSsl.ENCRYPT_MODE, KEY, IV); + cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV)); // Require direct buffers ByteBuffer input = ByteBuffer.allocate(1024); @@ -123,13 +124,14 @@ public void testDoFinalArguments() throws Exception { .getInstance("AES/CTR/NoPadding"); Assert.assertNotNull(cipher); - cipher.init(OpenSsl.ENCRYPT_MODE, KEY, IV); + cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(IV)); // Require direct buffer + ByteBuffer input = ByteBuffer.allocate(1024); ByteBuffer output = ByteBuffer.allocate(1024); try { - cipher.doFinal(output); + cipher.doFinal(input, output); Assert.fail("Output buffer should be direct buffer."); } catch (IllegalArgumentException e) { Assert.assertTrue(e.getMessage().contains( @@ -147,7 +149,7 @@ public void testInvalidKey() throws Exception { final byte[] invalidKey = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; try { - cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, IV); + cipher.init(OpenSsl.ENCRYPT_MODE, invalidKey, new IvParameterSpec(IV)); Assert.fail("java.security.InvalidKeyException should be thrown."); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Invalid AES key length: " + invalidKey.length + " bytes")); @@ -165,7 +167,7 @@ public void testInvalidIV() throws Exception { final byte[] invalidIV = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x11 }; try { - cipher.init(OpenSsl.ENCRYPT_MODE, KEY, invalidIV); + cipher.init(OpenSsl.ENCRYPT_MODE, KEY, new IvParameterSpec(invalidIV)); Assert.fail("java.security.InvalidAlgorithmParameterException should be thrown."); } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Wrong IV length: must be 16 bytes long"));