org.jitsi.srtp.crypto.OpenSslAesCipherSpi Maven / Gradle / Ivy
/*
* Copyright @ 2016 - present 8x8, Inc
*
* Licensed 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.jitsi.srtp.crypto;
import java.lang.ref.Cleaner.*;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
/**
* AES Cipher implementations using OpenSSL via JNI.
*/
public abstract class OpenSslAesCipherSpi
extends CipherSpi implements AutoCloseable
{
protected static class OpenSslAesCipherSpiCleanable implements Runnable
{
/**
* the OpenSSL EVP_CIPHER_CTX context
*/
long ptr;
@Override
public void run()
{
if (ptr != 0)
{
OpenSslAesCipherSpi.EVP_CIPHER_CTX_free(ptr);
ptr = 0;
}
}
}
protected static final int BLKLEN = 16;
private static native long EVP_CIPHER_CTX_new();
private static native void EVP_CIPHER_CTX_free(long ctx);
/* Note: Native methods that take the 'ctx' (other than _free) need to be non-static,
* to stop the Java GC from collecting the object (and thus running its Cleanable)
* while the native methods are still executing.
*/
private native boolean EVP_CipherInit(long ctx, long type,
byte[] key, byte[] iv, int enc);
private native boolean EVP_CipherUpdate(long ctx,
byte[] in, int inOffset, int len, byte[] out, int outOffset);
/**
* The current key used for this cipher.
*/
private Key key;
/**
* The current mode of this cipher.
*/
private final String cipherMode;
/**
* the OpenSSL EVP_CIPHER_CTX context
*/
protected final OpenSslAesCipherSpiCleanable ctx;
/**
* Cleanable registration of the EVP_CIPHER_CTX context.
*/
private final Cleanable ctxCleanable;
/**
* The most recent initialization vector set
*/
protected byte[] iv;
/**
* The Cipher operation mode with which the cipher was initialized.
*/
protected int opmode = 0;
protected OpenSslAesCipherSpi(String cipherMode)
{
if (!JitsiOpenSslProvider.isLoaded())
{
throw new RuntimeException("OpenSSL wrapper not loaded");
}
ctx = new OpenSslAesCipherSpiCleanable();
ctx.ptr = EVP_CIPHER_CTX_new();
if (ctx.ptr == 0)
{
throw new RuntimeException("EVP_CIPHER_CTX_create");
}
ctxCleanable = JitsiOpenSslProvider.CLEANER.register(this, ctx);
this.cipherMode = cipherMode;
}
@Override
public void engineSetMode(String mode) throws NoSuchAlgorithmException
{
if (!cipherMode.equalsIgnoreCase(mode))
{
throw new NoSuchAlgorithmException("Unsupported mode " + mode);
}
}
/**
* Get the appropriate OpenSSL cipher object for the given key length.
*/
protected abstract long getOpenSSLCipher(Key key)
throws InvalidKeyException;
@Override
public void engineSetPadding(String padding)
throws NoSuchPaddingException
{
if (!"nopadding".equalsIgnoreCase(padding))
{
throw new NoSuchPaddingException("No padding support");
}
}
@Override
protected int engineGetBlockSize()
{
return BLKLEN;
}
protected int getOutputSize(int inputLen, boolean forFinal)
{
return inputLen;
}
@Override
protected int engineGetOutputSize(int inputLen)
{
return getOutputSize(inputLen, true);
}
@Override
protected byte[] engineGetIV()
{
return Arrays.copyOf(iv, iv.length);
}
@Override
protected AlgorithmParameters engineGetParameters()
{
/* Not used by jitsi-srtp. */
throw new UnsupportedOperationException("Not implemented");
}
@Override
protected void engineInit(int opmode, Key key, SecureRandom random)
throws InvalidKeyException
{
try
{
engineInit(opmode, key, (AlgorithmParameterSpec) null, random);
}
catch (InvalidAlgorithmParameterException e)
{
throw new InvalidKeyException("could not create params", e);
}
}
protected void doEngineInit(int opmode, Key key)
throws InvalidKeyException, InvalidAlgorithmParameterException
{
int openSslEncryptMode;
switch (opmode)
{
case Cipher.ENCRYPT_MODE:
openSslEncryptMode = 1;
break;
case Cipher.DECRYPT_MODE:
openSslEncryptMode = 0;
break;
default:
throw new InvalidAlgorithmParameterException("Unsupported opmode " + opmode);
}
this.opmode = opmode;
byte[] keyParam = null;
long cipherType = 0;
if (key != this.key)
{
if (!key.getAlgorithm().equalsIgnoreCase("AES")
|| !key.getFormat().equalsIgnoreCase("RAW")
|| (key.getEncoded().length != 16
&& key.getEncoded().length != 24
&& key.getEncoded().length != 32))
{
throw new InvalidKeyException(
"AES SecretKeySpec expected, got " + key.getEncoded().length
+ " " + key.getAlgorithm() + "/" + key.getFormat());
}
this.key = key;
keyParam = key.getEncoded();
cipherType = getOpenSSLCipher(key);
}
if (!EVP_CipherInit(ctx.ptr, cipherType, keyParam, this.iv, openSslEncryptMode))
{
throw new InvalidKeyException("EVP_CipherInit");
}
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameters params,
SecureRandom random)
{
/* Not used by jitsi-srtp - parameters are always set externally. */
throw new UnsupportedOperationException("Not implemented");
}
@Override
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen)
{
int bufNeeded = getOutputSize(inputLen, false);
byte[] buf = new byte[bufNeeded];
try
{
int len = engineUpdate(input, inputOffset, inputLen, buf, 0);
assert(len == bufNeeded);
}
catch (ShortBufferException e)
{
/* Shouldn't happen, we allocated enough. */
throw new IllegalStateException(e);
}
return buf;
}
/**
* Call EVP_CipherUpdate, throwing on failure.
*/
protected void doCipherUpdate(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset)
{
if (!EVP_CipherUpdate(ctx.ptr, input, inputOffset, inputLen, output,
outputOffset))
{
throw new IllegalStateException("Failure in EVP_CipherUpdate");
}
}
@Override
protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) throws ShortBufferException
{
int needed = getOutputSize(inputLen, false);
if (output.length - outputOffset < needed)
{
throw new ShortBufferException("Output buffer needs at least " +
needed + "bytes");
}
if (inputOffset + inputLen > input.length)
{
throw new IllegalArgumentException("Input buffer length " + input.length +
" is too short for offset " + inputOffset + " plus length " + inputLen);
}
doCipherUpdate(input, inputOffset, inputLen, output, outputOffset);
return inputLen;
}
@Override
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws AEADBadTagException
{
int bufNeeded = getOutputSize(inputLen, true);
byte[] buf = new byte[bufNeeded];
try
{
int len = engineDoFinal(input, inputOffset, inputLen, buf, 0);
assert(len == bufNeeded);
}
catch (ShortBufferException e)
{
/* Shouldn't happen, we allocated enough. */
throw new IllegalStateException(e);
}
return buf;
}
@Override
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
byte[] output, int outputOffset) throws
ShortBufferException, AEADBadTagException
{
int needed = getOutputSize(inputLen, false);
if (output.length - outputOffset < needed)
{
throw new ShortBufferException("Output buffer needs at least " +
needed + "bytes");
}
if (inputOffset + inputLen > input.length)
{
throw new IllegalArgumentException("Input buffer length " + input.length +
" is too short for offset " + inputOffset + " plus length " + inputLen);
}
doCipherUpdate(input, inputOffset, inputLen, output, outputOffset);
return inputLen;
}
/**
* {@inheritDoc}
*/
@Override
public void close()
{
ctxCleanable.clean();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy