package org.bouncycastle.crypto.engines;
import java.security.SecureRandom;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.Wrapper;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.Arrays;
public class RFC3211WrapEngine
implements Wrapper
{
private CBCBlockCipher engine;
private ParametersWithIV param;
private boolean forWrapping;
private SecureRandom rand;
public RFC3211WrapEngine(BlockCipher engine)
{
this.engine = new CBCBlockCipher(engine);
}
public void init(
boolean forWrapping,
CipherParameters param)
{
this.forWrapping = forWrapping;
if (param instanceof ParametersWithRandom)
{
ParametersWithRandom p = (ParametersWithRandom)param;
rand = p.getRandom();
if (!(p.getParameters() instanceof ParametersWithIV))
{
throw new IllegalArgumentException("RFC3211Wrap requires an IV");
}
this.param = (ParametersWithIV)p.getParameters();
}
else
{
if (forWrapping)
{
rand = CryptoServicesRegistrar.getSecureRandom();
}
if (!(param instanceof ParametersWithIV))
{
throw new IllegalArgumentException("RFC3211Wrap requires an IV");
}
this.param = (ParametersWithIV)param;
}
}
public String getAlgorithmName()
{
return engine.getUnderlyingCipher().getAlgorithmName() + "/RFC3211Wrap";
}
public byte[] wrap(
byte[] in,
int inOff,
int inLen)
{
if (!forWrapping)
{
throw new IllegalStateException("not set for wrapping");
}
if (inLen > 255 || inLen < 0)
{
throw new IllegalArgumentException("input must be from 0 to 255 bytes");
}
engine.init(true, param);
int blockSize = engine.getBlockSize();
byte[] cekBlock;
if (inLen + 4 < blockSize * 2)
{
cekBlock = new byte[blockSize * 2];
}
else
{
cekBlock = new byte[(inLen + 4) % blockSize == 0 ? inLen + 4 : ((inLen + 4) / blockSize + 1) * blockSize];
}
cekBlock[0] = (byte)inLen;
System.arraycopy(in, inOff, cekBlock, 4, inLen);
byte[] pad = new byte[cekBlock.length - (inLen + 4)];
rand.nextBytes(pad);
System.arraycopy(pad, 0, cekBlock, inLen + 4, pad.length);
cekBlock[1] = (byte)~cekBlock[4];
cekBlock[2] = (byte)~cekBlock[4 + 1];
cekBlock[3] = (byte)~cekBlock[4 + 2];
for (int i = 0; i < cekBlock.length; i += blockSize)
{
engine.processBlock(cekBlock, i, cekBlock, i);
}
for (int i = 0; i < cekBlock.length; i += blockSize)
{
engine.processBlock(cekBlock, i, cekBlock, i);
}
return cekBlock;
}
public byte[] unwrap(
byte[] in,
int inOff,
int inLen)
throws InvalidCipherTextException
{
if (forWrapping)
{
throw new IllegalStateException("not set for unwrapping");
}
int blockSize = engine.getBlockSize();
if (inLen < 2 * blockSize)
{
throw new InvalidCipherTextException("input too short");
}
byte[] cekBlock = new byte[inLen];
byte[] iv = new byte[blockSize];
System.arraycopy(in, inOff, cekBlock, 0, inLen);
System.arraycopy(in, inOff, iv, 0, iv.length);
engine.init(false, new ParametersWithIV(param.getParameters(), iv));
for (int i = blockSize; i < cekBlock.length; i += blockSize)
{
engine.processBlock(cekBlock, i, cekBlock, i);
}
System.arraycopy(cekBlock, cekBlock.length - iv.length, iv, 0, iv.length);
engine.init(false, new ParametersWithIV(param.getParameters(), iv));
engine.processBlock(cekBlock, 0, cekBlock, 0);
engine.init(false, param);
for (int i = 0; i < cekBlock.length; i += blockSize)
{
engine.processBlock(cekBlock, i, cekBlock, i);
}
boolean invalidLength = ((cekBlock[0] & 0xff) > cekBlock.length - 4);
byte[] key;
if (invalidLength)
{
key = new byte[cekBlock.length - 4];
}
else
{
key = new byte[cekBlock[0] & 0xff];
}
System.arraycopy(cekBlock, 4, key, 0, key.length);
int nonEqual = 0;
for (int i = 0; i != 3; i++)
{
byte check = (byte)~cekBlock[1 + i];
nonEqual |= (check ^ cekBlock[4 + i]);
}
Arrays.clear(cekBlock);
if (nonEqual != 0 | invalidLength)
{
throw new InvalidCipherTextException("wrapped key corrupted");
}
return key;
}
}