/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.impl.security.crypto.jce;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.Wrapper;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.ccnx.ccn.impl.support.Log;
/**
* RFC3394 requires that the key to be wrapped be a multiple of 8 bytes
* in length. This poses challenges when wrapping private or public keys.
* draft-housley-aes-key-wrap-with-pad-02.txt modifies RFC3394 to add
* padding bytes, as supported in RFC3394 to remove this restriction.
* This is an implementation of that Internet-Draft, which is not yet
* supported by BouncyCastle.
*
* Code relies on BouncyCastle library for most of its infrastructure.
*/
public class RFC3394WrapWithPadEngine implements Wrapper {
private BlockCipher _engine;
private KeyParameter _parameters;
private boolean _forWrapping;
// Alternative IV is 64 bits, but changed from RFC3394. Top 8 bytes are
// these, bottom 8 bytes are big-endian representation of length of key to be
// wrapped.
private int FIXED_IV = 4;
private byte _iv[] = {(byte)0xA6, (byte)0x59, (byte)0x59, (byte)0xA6,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
public RFC3394WrapWithPadEngine(BlockCipher blockcipher) {
_engine = blockcipher;
}
public void init(boolean flag, CipherParameters cipherparameters)
{
_forWrapping = flag;
if (cipherparameters instanceof ParametersWithRandom) {
cipherparameters = ((ParametersWithRandom)cipherparameters).getParameters();
}
if (cipherparameters instanceof KeyParameter) {
_parameters = (KeyParameter)cipherparameters;
} else if (cipherparameters instanceof ParametersWithIV) {
_iv = ((ParametersWithIV)cipherparameters).getIV();
_parameters = (KeyParameter)((ParametersWithIV)cipherparameters).getParameters();
if (_iv.length != 8) {
throw new IllegalArgumentException("IV length not equal to 8");
}
}
}
public String getAlgorithmName() {
return _engine.getAlgorithmName();
}
public byte[] wrap(byte[] input, int offset, int length) {
if (!_forWrapping) {
throw new IllegalStateException("Not initialized for wrapping!");
}
byte [] lengthbytes = new byte[] { (byte)(length>>24), (byte)(length>>16), (byte)(length>>8), (byte)length};
System.arraycopy(lengthbytes, 0, _iv, FIXED_IV, lengthbytes.length);
int n = length / 8;
Log.info("wrap: wrapping key of length " + length + ", "+ n + " blocks.");
if ((n * 8) != length) {
// pad up to a multiple of 8 bytes
n++;
byte [] paddedinput = new byte[n*8];
System.arraycopy(input, offset, paddedinput, 0, length);
Log.info("RFC3394WrapWithPadEngine: adding padding of " + (paddedinput.length - input.length) + " bytes.");
// this leaves the last bytes of padded input containing sufficient 0 bytes to pad to
// a multiple of 64 bits
input = paddedinput;
length = paddedinput.length;
}
byte[] block = new byte[length + _iv.length];
byte[] buf = new byte[8 + _iv.length];
System.arraycopy(_iv, 0, block, 0, _iv.length);
System.arraycopy(input, 0, block, _iv.length, length);
_engine.init(true, _parameters);
for (int j = 0; j != 6; j++) {
for (int i = 1; i <= n; i++) {
System.arraycopy(block, 0, buf, 0, _iv.length);
System.arraycopy(block, 8 * i, buf, _iv.length, 8);
_engine.processBlock(buf, 0, buf, 0);
int t = n * j + i;
for (int k = 1; t != 0; k++) {
byte v = (byte)t;
buf[_iv.length - k] ^= v;
t >>>= 8;
}
System.arraycopy(buf, 0, block, 0, 8);
System.arraycopy(buf, 8, block, 8 * i, 8);
}
}
return block;
}
public byte[] unwrap(byte [] input, int offset, int length)
throws InvalidCipherTextException {
if (_forWrapping) {
throw new IllegalStateException("Not initialized for unwrapping!");
}
int n = length / 8;
if ((n * 8) != length) {
throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes");
}
byte[] block = new byte[length - _iv.length];
byte[] a = new byte[_iv.length];
byte[] buf = new byte[8 + _iv.length];
System.arraycopy(input, 0, a, 0, _iv.length);
System.arraycopy(input, _iv.length, block, 0, length - _iv.length);
_engine.init(false, _parameters);
n = n - 1;
for (int j = 5; j >= 0; j--) {
for (int i = n; i >= 1; i--) {
System.arraycopy(a, 0, buf, 0, _iv.length);
System.arraycopy(block, 8 * (i - 1), buf, _iv.length, 8);
int t = n * j + i;
for (int k = 1; t != 0; k++) {
byte v = (byte)t;
buf[_iv.length - k] ^= v;
t >>>= 8;
}
_engine.processBlock(buf, 0, buf, 0);
System.arraycopy(buf, 0, a, 0, 8);
System.arraycopy(buf, 8, block, 8 * (i - 1), 8);
}
}
for (int i = 0; i < FIXED_IV; i++) {
if (a[i] != _iv[i]) {
throw new InvalidCipherTextException("Checksum failed to verify!");
}
}
int expectedLength = (a[FIXED_IV] << 24) + ((a[FIXED_IV+1] & 0xFF) << 16)
+ ((a[FIXED_IV+2] & 0xFF) << 8) + (a[FIXED_IV+3] & 0xFF);
int maxBlockLength = 8*n;
if ((expectedLength < (maxBlockLength-8)) || (expectedLength > maxBlockLength)) {
throw new InvalidCipherTextException("Invalid checksum length: got: " + block.length + " expected: " +
expectedLength + " max: " + maxBlockLength + " n: " + n);
}
int b = (maxBlockLength - expectedLength) % 8;
if (block.length != (expectedLength + b)) {
throw new InvalidCipherTextException("Invalid checksum length: got: " + block.length + " expected: " +
expectedLength + " b: " + b + " max: " + maxBlockLength + " n: " + n);
}
for (int i=expectedLength; i < block.length; ++i) {
if (block[i] != 0) {
throw new InvalidCipherTextException("Invalid padding: byte " + i + " is " + Integer.toHexString(0x000000FF&block[i]));
}
}
// Strip padding
if (b > 0) {
byte [] trimmedBlock = new byte[expectedLength];
System.arraycopy(block, 0, trimmedBlock, 0, expectedLength);
block = trimmedBlock;
}
return block;
}
}